mirror of
https://github.com/jaandrle/deka-dom-el
synced 2025-07-01 20:32:13 +02:00
Compare commits
12 Commits
9a5909eb90
...
v0.9.5-alp
Author | SHA1 | Date | |
---|---|---|---|
f2c85ec983 | |||
4c450ae763 | |||
04f93345f8
|
|||
5076771410 | |||
f0dfdfde54 | |||
25d475ec04 | |||
e1f321004d | |||
47c5fda8d6
|
|||
7f3b818fa5
|
|||
d742d960ac | |||
d56d5e45d5
|
|||
4366027658 |
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ":bug: "
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bug Description
|
||||||
|
<!-- A clear and concise description of what the bug is -->
|
||||||
|
|
||||||
|
## Steps to Reproduce
|
||||||
|
<!-- Steps to reproduce the behavior -->
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
## Expected Behavior
|
||||||
|
<!-- A clear and concise description of what you expected to happen -->
|
||||||
|
|
||||||
|
## Actual Behavior
|
||||||
|
<!-- A clear and concise description of what actually happened -->
|
||||||
|
|
||||||
|
## Code Sample
|
||||||
|
<!-- If applicable, add minimal code sample to reproduce the issue -->
|
||||||
|
```js
|
||||||
|
// Your code here
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
- Browser and version: <!-- e.g. Chrome 120, Firefox 120, Safari 17 -->
|
||||||
|
- OS: <!-- e.g. Windows 11, macOS Sonoma, Ubuntu 22.04 -->
|
||||||
|
- dd<el> version: <!-- e.g. 0.9.2 -->
|
||||||
|
- Other relevant details:
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
<!-- If applicable, add screenshots to help explain your problem -->
|
||||||
|
|
||||||
|
## Additional Context
|
||||||
|
<!-- Add any other context about the problem here -->
|
22
.github/ISSUE_TEMPLATE/documentation.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/documentation.md
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
name: Documentation improvement
|
||||||
|
about: Suggest improvements to the documentation
|
||||||
|
title: ":abc: "
|
||||||
|
labels: documentation
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentation Area
|
||||||
|
<!-- Which part of the documentation needs improvement? Provide links if applicable -->
|
||||||
|
|
||||||
|
## Current Issue
|
||||||
|
<!-- What's currently unclear, missing, or incorrect in the documentation? -->
|
||||||
|
|
||||||
|
## Suggested Improvement
|
||||||
|
<!-- Describe the improvement or addition you'd like to see -->
|
||||||
|
|
||||||
|
## Example Content
|
||||||
|
<!-- If applicable, provide example content or wording -->
|
||||||
|
|
||||||
|
## Additional Context
|
||||||
|
<!-- Any other context or screenshots about the documentation request -->
|
29
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
29
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ":zap: "
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
<!-- Consider open discussion: https://github.com/jaandrle/deka-dom-el/discussions first -->
|
||||||
|
|
||||||
|
## Problem Statement
|
||||||
|
<!-- A clear and concise description of the problem this feature would solve -->
|
||||||
|
|
||||||
|
## Proposed Solution
|
||||||
|
<!-- A detailed description of the feature you're suggesting -->
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
<!-- Describe specific use cases where this feature would be beneficial -->
|
||||||
|
|
||||||
|
## Example Implementation
|
||||||
|
<!-- If possible, provide example code or pseudocode for how this feature might work -->
|
||||||
|
```js
|
||||||
|
// Example code
|
||||||
|
```
|
||||||
|
|
||||||
|
## Alternatives Considered
|
||||||
|
<!-- A description of any alternative solutions or features you've considered -->
|
||||||
|
|
||||||
|
## Additional Context
|
||||||
|
<!-- Any other context, screenshots, or examples that might be helpful -->
|
39
.github/pull_request_template.md
vendored
Normal file
39
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<!--
|
||||||
|
Please use an appropriate git3moji in your PR title: https://robinpokorny.github.io/git3moji/
|
||||||
|
Examples:
|
||||||
|
- :bug: Fix signal update not triggering on nested properties
|
||||||
|
- :zap: Improve event delegation performance
|
||||||
|
- :abc: Add documentation for custom elements
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
<!-- Describe the changes introduced by this PR -->
|
||||||
|
|
||||||
|
## Related Issues
|
||||||
|
<!-- Link any related issues using the format #ISSUE_NUMBER -->
|
||||||
|
|
||||||
|
## Type of Change
|
||||||
|
- [ ] Bug fix (non-breaking change that fixes an issue)
|
||||||
|
- [ ] New feature (non-breaking change that adds functionality)
|
||||||
|
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||||
|
- [ ] Documentation update
|
||||||
|
- [ ] Code refactoring
|
||||||
|
- [ ] Performance improvement
|
||||||
|
- [ ] Test update
|
||||||
|
|
||||||
|
## Testing Performed
|
||||||
|
<!-- Describe the tests you've done to verify your changes -->
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
<!-- If applicable, add screenshots to help explain your changes -->
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
- [ ] My code follows the code style of this project
|
||||||
|
- [ ] I have performed a self-review of my own code
|
||||||
|
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||||
|
- [ ] I have updated the documentation accordingly
|
||||||
|
- [ ] My changes generate no new warnings
|
||||||
|
- [ ] All existing tests are passing
|
||||||
|
|
||||||
|
## Additional Notes
|
||||||
|
<!-- Any additional information that might be helpful for reviewers -->
|
18
.github/workflows/npm-publish.yml
vendored
Normal file
18
.github/workflows/npm-publish.yml
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
name: Publish Package to npmjs
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
release:
|
||||||
|
types: [created]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||||
|
with:
|
||||||
|
node-version: '20.16'
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
- run: npm ci
|
||||||
|
- run: npm publish
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
3
.npmrc
Normal file
3
.npmrc
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}
|
||||||
|
registry=https://registry.npmjs.org/
|
||||||
|
always-auth=true
|
134
CODE_OF_CONDUCT.md
Normal file
134
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
|
||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||||
|
identity and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the overall
|
||||||
|
community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||||
|
any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email address,
|
||||||
|
without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
andrle.jan@centrum.cz.
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series of
|
||||||
|
actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or permanent
|
||||||
|
ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||||
|
community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.1, available at
|
||||||
|
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by
|
||||||
|
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||||
|
[https://www.contributor-covenant.org/translations][translations].
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||||
|
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||||
|
[FAQ]: https://www.contributor-covenant.org/faq
|
||||||
|
[translations]: https://www.contributor-covenant.org/translations
|
||||||
|
|
177
CONTRIBUTING.md
Normal file
177
CONTRIBUTING.md
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
# Contributing to Deka DOM Elements
|
||||||
|
|
||||||
|
Thank you for your interest in contributing to Deka DOM Elements (dd<el> or DDE)! This document provides guidelines and
|
||||||
|
instructions for contributing to the project.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Code of Conduct](#code-of-conduct)
|
||||||
|
- [Getting Started](#getting-started)
|
||||||
|
- [Development Workflow](#development-workflow)
|
||||||
|
- [Commit Guidelines](#commit-guidelines)
|
||||||
|
- [Pull Request Process](#pull-request-process)
|
||||||
|
- [Issue Guidelines](#issue-guidelines)
|
||||||
|
- [Coding Standards](#coding-standards)
|
||||||
|
- [Testing](#testing)
|
||||||
|
- [Documentation](#documentation)
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
Please be respectful and inclusive in your interactions with other contributors. We aim to foster a welcoming community
|
||||||
|
where everyone feels comfortable participating.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. **Fork the repository**:
|
||||||
|
- Click the "Fork" button on the GitHub repository
|
||||||
|
|
||||||
|
2. **Clone your fork**:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/YOUR-USERNAME/deka-dom-el.git
|
||||||
|
cd deka-dom-el
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Set up the development environment**:
|
||||||
|
```bash
|
||||||
|
npm ci
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Add the upstream repository**:
|
||||||
|
```bash
|
||||||
|
git remote add upstream https://github.com/jaandrle/deka-dom-el.git
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
1. **Create a new branch**:
|
||||||
|
```bash
|
||||||
|
git checkout -b your-feature-branch
|
||||||
|
```
|
||||||
|
Use descriptive branch names that reflect the changes you're making.
|
||||||
|
|
||||||
|
2. **Make your changes**:
|
||||||
|
- Write clean, modular code
|
||||||
|
- Follow the project's coding standards (see [Coding Standards](#coding-standards))
|
||||||
|
- Include relevant tests for your changes
|
||||||
|
|
||||||
|
3. ~**Run tests**:~
|
||||||
|
```bash
|
||||||
|
#npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Build the project**:
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
#or
|
||||||
|
bs/build.js
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Preview documentation changes** (if applicable):
|
||||||
|
```bash
|
||||||
|
npm run docs
|
||||||
|
#or
|
||||||
|
bs/docs.js
|
||||||
|
```
|
||||||
|
|
||||||
|
…see [BS folder](./bs/README.md) for more info.
|
||||||
|
|
||||||
|
## Categorizing [](https://robinpokorny.github.io/git3moji/) <!-- editorconfig-checker-disable-line -->
|
||||||
|
We use [git3moji](https://git3moji.netlify.app/) for commit messages, issue titles, pull request titles and in other
|
||||||
|
areas. To make categorizing quick and consistent.
|
||||||
|
|
||||||
|
## Commit Guidelines
|
||||||
|
|
||||||
|
We use [git3moji](https://git3moji.netlify.app/) for commit messages. This helps keep the commit history clear and
|
||||||
|
consistent.
|
||||||
|
|
||||||
|
```
|
||||||
|
:emoji: Short summary of the change
|
||||||
|
```
|
||||||
|
…for example:
|
||||||
|
|
||||||
|
```
|
||||||
|
:bug: Fix signal update not triggering on nested properties
|
||||||
|
:zap: Improve event delegation performance
|
||||||
|
:abc: Add documentation for custom elements
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pull Request Process
|
||||||
|
|
||||||
|
1. **Push your changes**:
|
||||||
|
```bash
|
||||||
|
git push origin your-feature-branch
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Open a Pull Request**:
|
||||||
|
- Go to the repository on GitHub
|
||||||
|
- Click "New Pull Request"
|
||||||
|
- Select your branch
|
||||||
|
- Provide a clear description of your changes
|
||||||
|
|
||||||
|
3. **PR Guidelines**:
|
||||||
|
- Use a clear, descriptive title with the appropriate git3moji
|
||||||
|
- Reference any related issues
|
||||||
|
- Explain what the changes do and why they are needed
|
||||||
|
- List any dependencies that are required for the change
|
||||||
|
- Include screenshots or examples if applicable
|
||||||
|
|
||||||
|
4. **Code Review**:
|
||||||
|
- Address any feedback from reviewers
|
||||||
|
- Make necessary changes and push to your branch
|
||||||
|
- The PR will be updated automatically
|
||||||
|
|
||||||
|
5. **Merge**:
|
||||||
|
- Once approved, a maintainer will merge your PR
|
||||||
|
- The main branch is protected, so you cannot push directly to it
|
||||||
|
|
||||||
|
## Issue Guidelines
|
||||||
|
|
||||||
|
When creating an issue, please use the appropriate template and include as much information as possible:
|
||||||
|
|
||||||
|
### Bug Reports
|
||||||
|
|
||||||
|
- Use the `:bug:` emoji in the title
|
||||||
|
- Clearly describe the issue
|
||||||
|
- Include steps to reproduce
|
||||||
|
- Mention your environment (browser, OS, etc.)
|
||||||
|
- Add screenshots if applicable
|
||||||
|
|
||||||
|
### Feature Requests
|
||||||
|
|
||||||
|
- Use the `:zap:` emoji in the title
|
||||||
|
- Describe the feature clearly
|
||||||
|
- Explain why it would be valuable
|
||||||
|
- Include examples or mockups if possible
|
||||||
|
|
||||||
|
### Documentation Improvements
|
||||||
|
|
||||||
|
- Use the `:abc:` emoji in the title
|
||||||
|
- Identify what documentation needs improvement
|
||||||
|
- Suggest specific changes or additions
|
||||||
|
|
||||||
|
## Coding Standards
|
||||||
|
|
||||||
|
- Follow the existing code style in the project
|
||||||
|
- Use meaningful variable and function names
|
||||||
|
- Keep functions small and focused
|
||||||
|
- Add comments for complex logic
|
||||||
|
- Use TypeScript types appropriately
|
||||||
|
|
||||||
|
<!--
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- Add tests for new features
|
||||||
|
- Update tests for modified code
|
||||||
|
- Ensure all tests pass before submitting a PR
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- Update the documentation when you add or modify features
|
||||||
|
- Document both API usage and underlying concepts
|
||||||
|
- Use clear, concise language
|
||||||
|
- Include examples where appropriate
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Thank you for contributing to Deka DOM Elements! Your efforts help make the project better for everyone.
|
193
README.md
193
README.md
@ -1,56 +1,53 @@
|
|||||||
**WIP** (the experimentation phase)
|
**Alpha**
|
||||||
| [source code on GitHub](https://github.com/jaandrle/deka-dom-el)
|
| [Docs&Examples](https://jaandrle.github.io/deka-dom-el "Official documentation and guide site")
|
||||||
| [*mirrored* on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el)
|
| [NPM](https://www.npmjs.com/package/deka-dom-el "Official NPM package page")
|
||||||
|
| [GitHub](https://github.com/jaandrle/deka-dom-el "Official GitHub repository")
|
||||||
|
([*Gitea*](https://gitea.jaandrle.cz/jaandrle/deka-dom-el "GitHub repository mirror on my own Gitea instance"))
|
||||||
|
|
||||||
|
***Vanilla for flavouring — a full-fledged feast for large projects***
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 🌟 Reactive component with clear separation of concerns
|
||||||
|
document.body.append(
|
||||||
|
el(EmojiCounter, { initial: "🚀" }),
|
||||||
|
);
|
||||||
|
|
||||||
|
function EmojiCounter({ initial }) {
|
||||||
|
// ✨ - Define reactive data
|
||||||
|
const count = S(0);
|
||||||
|
const emoji = S(initial);
|
||||||
|
const textContent = S(() => `Hello World ${emoji.get().repeat(count.get())}`);
|
||||||
|
|
||||||
|
// 🔄 - UI updates automatically when signals change
|
||||||
|
return el().append(
|
||||||
|
el("p", { textContent, className: "output" }),
|
||||||
|
|
||||||
|
// 🎮 - Update state on events
|
||||||
|
el("button", { textContent: "Add Emoji" },
|
||||||
|
on("click", () => count.set(count.get() + 1)),
|
||||||
|
),
|
||||||
|
|
||||||
|
el("select", null,
|
||||||
|
on.defer(el=> el.value= initial),
|
||||||
|
on("change", e => emoji.set(e.target.value)),
|
||||||
|
).append(
|
||||||
|
el(Option, "🎉"),
|
||||||
|
el(Option, "🚀"),
|
||||||
|
el(Option, "💖"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function Option({ textContent }){
|
||||||
|
return el("option", { value: textContent, textContent });
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*…use simple DOM API by default and library tools and logic when you need them*
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="docs/assets/logo.svg" alt="Deka DOM Elements Logo" width="180" height="180">
|
<img src="docs/assets/logo.svg" alt="Deka DOM Elements Logo" width="180" height="180">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# Deka DOM Elements
|
# Deka DOM Elements (dd\<el\> or DDE)
|
||||||
|
|
||||||
***Vanilla for flavouring — a full-fledged feast for large projects***
|
|
||||||
|
|
||||||
*…use simple DOM API by default and library tools and logic when you need them*
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// 🌟 Reactive component with clear separation of concerns
|
|
||||||
document.body.append(
|
|
||||||
el(EmojiCounter, { initial: "🚀" })
|
|
||||||
);
|
|
||||||
|
|
||||||
function EmojiCounter({ initial }) {
|
|
||||||
// ✨ State - Define reactive data
|
|
||||||
const count = S(0);
|
|
||||||
const emoji = S(initial);
|
|
||||||
|
|
||||||
/** @param {HTMLOptionElement} el */
|
|
||||||
const isSelected= el=> (el.selected= el.value===initial);
|
|
||||||
|
|
||||||
// 🔄 View - UI updates automatically when signals change
|
|
||||||
return el().append(
|
|
||||||
el("p", {
|
|
||||||
className: "output",
|
|
||||||
textContent: S(() =>
|
|
||||||
`Hello World ${emoji.get().repeat(clicks.get())}`),
|
|
||||||
}),
|
|
||||||
|
|
||||||
// 🎮 Controls - Update state on events
|
|
||||||
el("button", { textContent: "Add Emoji" },
|
|
||||||
on("click", () => count.set(count.get() + 1))
|
|
||||||
),
|
|
||||||
|
|
||||||
el("select", null, on("change", e => emoji.set(e.target.value)))
|
|
||||||
.append(
|
|
||||||
el(Option, "🎉", isSelected),
|
|
||||||
el(Option, "🚀", isSelected),
|
|
||||||
el(Option, "💖", isSelected),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function Option({ textContent }){
|
|
||||||
return Ol("option", { value: textContent, textContent });
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Creating reactive elements, components, and Web Components using the native
|
Creating reactive elements, components, and Web Components using the native
|
||||||
[IDL](https://developer.mozilla.org/en-US/docs/Glossary/IDL)/JavaScript DOM API enhanced with
|
[IDL](https://developer.mozilla.org/en-US/docs/Glossary/IDL)/JavaScript DOM API enhanced with
|
||||||
@ -59,19 +56,57 @@ Creating reactive elements, components, and Web Components using the native
|
|||||||
## Features at a Glance
|
## Features at a Glance
|
||||||
|
|
||||||
- ✅ **No build step required** — use directly in browsers or Node.js
|
- ✅ **No build step required** — use directly in browsers or Node.js
|
||||||
- ☑️ **Lightweight** — ~10-15kB minified (original goal 10kB) with zero/minimal dependencies
|
- ✅ **Minimalized footprint** — ~10-15kB minified bundle (original goal 10kB), **zero**/minimal dependencies and
|
||||||
- ✅ **Declarative & functional approach** for clean, maintainable code
|
small in-memory size (auto-releasing resources as much as possible)
|
||||||
- ✅ **Optional signals** with support for custom reactive implementations
|
- ✅ **Declarative & functional approach support** for clean, maintainable code
|
||||||
- ✅ **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom)
|
- ✅ **Signals and events** for reactive UI
|
||||||
- 🔄 **TypeScript support** (work in progress)
|
- ✅ **Memoization for performance** — optimize rendering with intelligent caching
|
||||||
- 🔄 **Enhanced Web Components** support (work in progress)
|
- ☑️ **Optional build-in signals** with support for custom reactive implementations (#39)
|
||||||
|
- ☑️ **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom)
|
||||||
|
- ✅ **TypeScript support**
|
||||||
|
- ✅ **Support for debugging with browser DevTools** without extensions
|
||||||
|
- ☑️ **Enhanced Web Components** support
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Quick Links
|
||||||
|
|
||||||
|
- [**Documentation and Guide**](https://jaandrle.github.io/deka-dom-el)
|
||||||
|
- [**Examples**](https://jaandrle.github.io/deka-dom-el/p15-examples.html)
|
||||||
|
- [**Changelog**](https://github.com/jaandrle/deka-dom-el/releases)
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install deka-dom-el --save
|
||||||
|
```
|
||||||
|
|
||||||
|
…or via CDN / Direct Script:
|
||||||
|
|
||||||
|
For CDN links and various build formats (ESM/IIFE, with/without signals, minified/unminified), see the [interactive
|
||||||
|
format selector](https://jaandrle.github.io/deka-dom-el/#h-getting-started) on the documentation site.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Example with IIFE build (creates a global DDE object) -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/iife-with-signals.min.js"></script>
|
||||||
|
<script>
|
||||||
|
const { el, S } = DDE;
|
||||||
|
// Your code here
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Or with ES modules -->
|
||||||
|
<script type="module">
|
||||||
|
import { el, S } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.min.js";
|
||||||
|
// Your code here
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
## Why Another Library?
|
## Why Another Library?
|
||||||
|
|
||||||
This library bridges the gap between minimal solutions like van/hyperscript and more comprehensive frameworks like
|
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.
|
[solid-js](https://github.com/solidjs/solid), offering a balanced trade-off between size, complexity, and usability.
|
||||||
|
|
||||||
Following functional programming principles, Deka DOM Elements starts with pure JavaScript (DOM API) and gradually adds
|
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
|
auxiliary functions. These range from minor improvements to advanced features for building complete declarative
|
||||||
reactive UI templates.
|
reactive UI templates.
|
||||||
|
|
||||||
@ -79,29 +114,6 @@ A key advantage: any internal function (`assign`, `classListDeclarative`, `on`,
|
|||||||
independently while also working seamlessly together. This modular approach makes it easier to integrate the library
|
independently while also working seamlessly together. This modular approach makes it easier to integrate the library
|
||||||
into existing projects.
|
into existing projects.
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
#### npm
|
|
||||||
```bash
|
|
||||||
# TBD
|
|
||||||
# npm install deka-dom-el
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Direct Script
|
|
||||||
```html
|
|
||||||
<script src="https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/dde-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:
|
||||||
@ -111,11 +123,24 @@ Signals are the reactive backbone of Deka DOM Elements:
|
|||||||
- [TC39 Signals Proposal](https://github.com/tc39/proposal-signals) (future standard)
|
- [TC39 Signals Proposal](https://github.com/tc39/proposal-signals) (future standard)
|
||||||
- [Observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) (underlying concept)
|
- [Observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) (underlying concept)
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
We welcome contributions from the community! Please see our [Contributing Guide](CONTRIBUTING.md) for details on how to
|
||||||
|
get started, coding standards, commit guidelines, and the pull request process.
|
||||||
|
|
||||||
## Inspiration and Alternatives
|
## Inspiration and Alternatives
|
||||||
|
|
||||||
- [vanjs-org/van](https://github.com/vanjs-org/van) - World's smallest reactive UI framework
|
- [vanjs-org/van](https://github.com/vanjs-org/van) — World's smallest reactive UI framework
|
||||||
- [adamhaile/S](https://github.com/adamhaile/S) - Simple, clean, fast reactive programming
|
- [adamhaile/S](https://github.com/adamhaile/S) — Simple, clean, fast reactive programming
|
||||||
- [hyperhype/hyperscript](https://github.com/hyperhype/hyperscript) - Create HyperText with JavaScript
|
- [hyperhype/hyperscript](https://github.com/hyperhype/hyperscript) — Create HyperText with JavaScript
|
||||||
- [potch/signals](https://github.com/potch/signals) - A small reactive signals library
|
- [potch/signals](https://github.com/potch/signals) — A small reactive signals library
|
||||||
- [jaandrle/dollar_dom_component](https://github.com/jaandrle/dollar_dom_component) -
|
- [AseasRoa/paintor](https://github.com/AseasRoa/paintor) - JavaScript library for building reactive client-side user
|
||||||
Functional DOM components without JSX/virtual DOM
|
interfaces or HTML code.
|
||||||
|
- [pota](https://pota.quack.uy/) — small and pluggable Reactive Web Renderer. It's compiler-less, includes an html
|
||||||
|
function, and a optimized babel preset in case you fancy JSX.
|
||||||
|
- [TarekRaafat/eleva](https://github.com/TarekRaafat/eleva) — A minimalist, lightweight, pure vanilla JavaScript
|
||||||
|
frontend runtime framework.
|
||||||
|
- [didi/mpx](https://github.com/didi/mpx) — Mpx,一款具有优秀开发体验和深度性能优化的增强型跨端小程序框架
|
||||||
|
- [mxjp/rvx](https://github.com/mxjp/rvx) — A signal based frontend framework
|
||||||
|
- [jaandrle/dollar_dom_component](https://github.com/jaandrle/dollar_dom_component) —
|
||||||
|
Functional DOM components without JSX/virtual DOM (my old library)
|
||||||
|
@ -2,15 +2,18 @@
|
|||||||
This project uses [jaandrle/bs: The simplest possible build system using executable/bash scripts](
|
This project uses [jaandrle/bs: The simplest possible build system using executable/bash scripts](
|
||||||
https://github.com/jaandrle/bs).
|
https://github.com/jaandrle/bs).
|
||||||
|
|
||||||
#### bs/build.js [--minify|--help]
|
#### bs/build.js [main|signals] [--no-types|--help]
|
||||||
Generates alternative versions of the project (other than native ESM code).
|
Generates alternative versions of the project (other than native ESM code).
|
||||||
Also generates typescript definitions.
|
Also generates typescript definitions.
|
||||||
|
|
||||||
#### bs/docs.js
|
#### bs/docs.js
|
||||||
Generates documentation, from `docs/`. Uses “SSR” technique, using deka-dom-el itself.
|
Generates documentation, from `docs/`. Uses “SSR” technique, using deka-dom-el itself.
|
||||||
|
|
||||||
|
For running use `npx serve dist/docs`.
|
||||||
|
|
||||||
#### bs/lint.sh
|
#### bs/lint.sh
|
||||||
Lints size of the project, jshint. See configs:
|
Lints size of the project, jshint. See configs:
|
||||||
|
|
||||||
- `package.json`: key `size-limit`
|
- `package.json`: key `size-limit`
|
||||||
- `package.json`: key `jshintConfig`
|
- `package.json`: key `jshintConfig`
|
||||||
|
- `.editorconfig`
|
||||||
|
17
bs/build.js
17
bs/build.js
@ -4,30 +4,33 @@ const files= [ "index", "index-with-signals" ];
|
|||||||
|
|
||||||
$.api("")
|
$.api("")
|
||||||
.command("main", "Build main files", { default: true })
|
.command("main", "Build main files", { default: true })
|
||||||
.action(async function main(){
|
.option("--no-types", "Also generate d.ts files", false)
|
||||||
const regular = await build({
|
.action(function main({ types }){
|
||||||
|
const regular = build({
|
||||||
files,
|
files,
|
||||||
filesOut,
|
filesOut,
|
||||||
minify: "no",
|
minify: "no",
|
||||||
|
types,
|
||||||
});
|
});
|
||||||
const min = await build({
|
const min = build({
|
||||||
files,
|
files,
|
||||||
filesOut(file, mark= "esm"){
|
filesOut(file, mark= "esm"){
|
||||||
const out= filesOut(file, mark);
|
const out= filesOut(file, mark);
|
||||||
const idx= out.lastIndexOf(".");
|
const idx= out.indexOf(".");
|
||||||
return out.slice(0, idx)+".min"+out.slice(idx);
|
return out.slice(0, idx)+".min"+out.slice(idx);
|
||||||
},
|
},
|
||||||
minify: "full",
|
minify: "full",
|
||||||
|
types,
|
||||||
});
|
});
|
||||||
return $.exit(regular + min);
|
return $.exit(regular + min);
|
||||||
})
|
})
|
||||||
.command("signals", "Build only signals (for example for analysis)")
|
.command("signals", "Build only signals (for example for analysis)")
|
||||||
.action(async function signals(){
|
.action(function signals(){
|
||||||
const regular = await build({
|
const regular = build({
|
||||||
files: [ "signals" ],
|
files: [ "signals" ],
|
||||||
filesOut(file){ return "dist/."+file; },
|
filesOut(file){ return "dist/."+file; },
|
||||||
minify: "no",
|
minify: "no",
|
||||||
dde: false,
|
iife: false,
|
||||||
});
|
});
|
||||||
return $.exit(regular);
|
return $.exit(regular);
|
||||||
})
|
})
|
||||||
|
128
bs/dev/.build.js
128
bs/dev/.build.js
@ -1,65 +1,101 @@
|
|||||||
#!/usr/bin/env -S npx nodejsscript
|
#!/usr/bin/env -S npx nodejsscript
|
||||||
import { bundle as bundleDTS } from "dts-bundler";
|
import { buildSync as esbuildSync } from "esbuild";
|
||||||
const css= echo.css`
|
const css= echo.css`
|
||||||
.info{ color: gray; }
|
.info{ color: gray; }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export async function build({ files, filesOut, minify= "partiala", dde= true }){
|
export function build({ files, filesOut, minify= "partial", iife= true, types= true }){
|
||||||
for(const file_root of files){
|
for(const file_root of files){
|
||||||
const file= file_root+".js";
|
const file= file_root+".js";
|
||||||
echo(`Processing ${file} (minified: ${minify})`);
|
echo(`Processing ${file} (minified: ${minify})`);
|
||||||
const out= filesOut(file);
|
const out= filesOut(file);
|
||||||
const esbuild_output= s.$().run([
|
esbuild({ file, out, minify });
|
||||||
"npx esbuild '::file::'",
|
|
||||||
"--platform=neutral",
|
if(types){
|
||||||
"--bundle",
|
const file_dts= file_root+".d.ts";
|
||||||
minifyOption(minify),
|
const file_dts_out= filesOut(file_dts);
|
||||||
"--legal-comments=inline",
|
echoVariant(file_dts_out, true);
|
||||||
"--packages=external",
|
buildDts({
|
||||||
"--outfile='::out::'"
|
bundle: out,
|
||||||
].filter(Boolean).join(" "), { file, out });
|
entry: file_dts,
|
||||||
if(esbuild_output.code)
|
});
|
||||||
return $.exit(esbuild_output.code, echo(esbuild_output.stderr));
|
echoVariant(file_dts_out);
|
||||||
echoVariant(esbuild_output.stderr.split("\n")[1].trim()+ " (esbuild)");
|
}
|
||||||
|
|
||||||
|
if(iife) toIIFE(file, file_root, types);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
function toIIFE(file, file_root, types){
|
||||||
|
const fileMark= "iife";
|
||||||
|
const name= "DDE";
|
||||||
|
const out= filesOut(file_root+".js", fileMark);
|
||||||
|
|
||||||
|
const params= {
|
||||||
|
format: "iife",
|
||||||
|
globalName: name
|
||||||
|
};
|
||||||
|
esbuild({ file, out, minify, params });
|
||||||
|
|
||||||
|
if(!types) return;
|
||||||
|
const file_dts= file_root+".d.ts";
|
||||||
|
const file_dts_out= filesOut(file_dts, fileMark);
|
||||||
|
echoVariant(file_dts_out, true);
|
||||||
|
buildDts({
|
||||||
|
name: fileMark,
|
||||||
|
bundle: out,
|
||||||
|
entry: file_dts,
|
||||||
|
})
|
||||||
|
echoVariant(file_dts_out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function buildDts({ bundle, entry, name }){
|
||||||
|
const out= bundle.slice(0, bundle.lastIndexOf("."))+".d.ts";
|
||||||
|
const dts_b_g_output= s.run([
|
||||||
|
"npx dts-bundle-generator",
|
||||||
|
"--silent",
|
||||||
|
"-o ::out::",
|
||||||
|
!name ? false : ("--umd-module-name "+name),
|
||||||
|
"--inline-declare-global",
|
||||||
|
"::entry::"
|
||||||
|
].filter(Boolean).join(" "), { out, entry });
|
||||||
|
return dts_b_g_output;
|
||||||
|
}
|
||||||
|
export function esbuild({ file, out, minify= "partial", params= {} }){
|
||||||
|
const esbuild_output= esbuildSync({
|
||||||
|
entryPoints: [file],
|
||||||
|
outfile: out,
|
||||||
|
platform: "neutral",
|
||||||
|
bundle: true,
|
||||||
|
legalComments: "inline",
|
||||||
|
packages: "external",
|
||||||
|
metafile: true,
|
||||||
|
...minifyOption(minify),
|
||||||
|
...params
|
||||||
|
});
|
||||||
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));
|
||||||
|
|
||||||
const file_dts= file_root+".d.ts";
|
echoVariant(metaToLineStatus(esbuild_output.metafile, out));
|
||||||
const file_dts_out= filesOut(file_dts);
|
return esbuild_output;
|
||||||
echoVariant(file_dts_out);
|
|
||||||
s.echo(bundleDTS(file_dts)).to(file_dts_out);
|
|
||||||
|
|
||||||
if(dde) await toDDE(out, file_root);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
async function toDDE(file, file_root){
|
|
||||||
const name= "dde";
|
|
||||||
const out= filesOut(file_root+".js", name);
|
|
||||||
echoVariant(`${out} (${file} → globalThis.${name})`)
|
|
||||||
|
|
||||||
let content= s.cat(file).toString().split(/export ?{/);
|
|
||||||
content.splice(1, 0, `\nglobalThis.${name}= {`);
|
|
||||||
content[2]= content[2]
|
|
||||||
.replace(/,(?!\n)/g, ",\n")
|
|
||||||
.replace(/(?<!\n)}/, "\n}")
|
|
||||||
.replace(/^(\t*)(.*) as ([^,\n]*)(,?)$/mg, "$1$3: $2$4");
|
|
||||||
s.echo([
|
|
||||||
`//deka-dom-el library is available via global namespace \`${name}\``,
|
|
||||||
"(()=> {",
|
|
||||||
content.join(""),
|
|
||||||
"})();"
|
|
||||||
].join("\n")).to(out);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/** @param {"no"|"full"|"partial"} level */
|
/** @param {"no"|"full"|"partial"} level */
|
||||||
function minifyOption(level= "partial"){
|
function minifyOption(level= "partial"){
|
||||||
if("no"===level) return undefined;
|
if("no"===level) return { minify: false };
|
||||||
if("full"===level) return "--minify";
|
if("full"===level) return { minify: true };
|
||||||
return "--minify-syntax --minify-identifiers";
|
return { minifySyntax: true, minifyIdentifiers: true };
|
||||||
}
|
}
|
||||||
function echoVariant(name){
|
function metaToLineStatus(meta, file){
|
||||||
return echo("%c✓ "+name, css.info+css);
|
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){
|
||||||
|
if(todo) return echo.use("-R", "~ "+name);
|
||||||
|
return echo("%c✓ "+name, css.info);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
/* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */// editorconfig-checker-disable-line
|
/* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */// editorconfig-checker-disable-line
|
||||||
echo("Building static documentation files…");
|
echo("Building static documentation files…");
|
||||||
echo("Preparing…");
|
echo("Preparing…");
|
||||||
import { path_target, pages as pages_registered, styles, dispatchEvent, t } from "../docs/ssr.js";
|
import { path_target, pages as pages_registered, styles, currentPageId, dispatchEvent, t } from "../docs/ssr.js";
|
||||||
import { createHTMl } from "./docs/jsdom.js";
|
import { createHTMl } from "./docs/jsdom.js";
|
||||||
import { register, queue } from "../jsdom.js";
|
import { register, queue } from "../jsdom.js";
|
||||||
const pkg= s.cat("package.json").xargs(JSON.parse);
|
const pkg= s.cat("package.json").xargs(JSON.parse);
|
||||||
@ -28,6 +28,7 @@ for(const { id, info } of pages){
|
|||||||
);
|
);
|
||||||
const { el }= await register(serverDOM.dom);
|
const { el }= await register(serverDOM.dom);
|
||||||
const { page }= await import(`../docs/${id}.html.js`);
|
const { page }= await import(`../docs/${id}.html.js`);
|
||||||
|
currentPageId(id)
|
||||||
serverDOM.document.body.append(
|
serverDOM.document.body.append(
|
||||||
el(page, { pkg, info }),
|
el(page, { pkg, info }),
|
||||||
);
|
);
|
||||||
|
11
bs/lint.sh
11
bs/lint.sh
@ -1,5 +1,12 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -eou pipefail
|
set -eou pipefail
|
||||||
npx editorconfig-checker -format gcc
|
# if $1=vim -no-color
|
||||||
|
one=${1:-''}
|
||||||
|
additional=''
|
||||||
|
[ "$one" = 'vim' ] && additional='-no-color'
|
||||||
|
npx editorconfig-checker -format gcc ${additional}
|
||||||
|
[ "$one" = 'vim' ] && additional='--reporter unix'
|
||||||
|
npx jshint index.js src ${additional}
|
||||||
|
[ "$one" = 'vim' ] && exit 0
|
||||||
npx size-limit
|
npx size-limit
|
||||||
npx jshint index.js src
|
npx publint
|
||||||
|
1012
dist/dde-with-signals.js
vendored
1012
dist/dde-with-signals.js
vendored
File diff suppressed because it is too large
Load Diff
31
dist/dde-with-signals.min.js
vendored
31
dist/dde-with-signals.min.js
vendored
File diff suppressed because one or more lines are too long
667
dist/dde.js
vendored
667
dist/dde.js
vendored
@ -1,667 +0,0 @@
|
|||||||
//deka-dom-el library is available via global namespace `dde`
|
|
||||||
(()=> {
|
|
||||||
// src/helpers.js
|
|
||||||
function isUndef(value) {
|
|
||||||
return typeof value === "undefined";
|
|
||||||
}
|
|
||||||
function isInstance(obj, cls) {
|
|
||||||
return obj instanceof cls;
|
|
||||||
}
|
|
||||||
function isProtoFrom(obj, cls) {
|
|
||||||
return Object.prototype.isPrototypeOf.call(cls, obj);
|
|
||||||
}
|
|
||||||
function oAssign(...o) {
|
|
||||||
return Object.assign(...o);
|
|
||||||
}
|
|
||||||
function onAbort(signal, listener) {
|
|
||||||
if (!signal || !isInstance(signal, AbortSignal))
|
|
||||||
return true;
|
|
||||||
if (signal.aborted)
|
|
||||||
return;
|
|
||||||
signal.addEventListener("abort", listener);
|
|
||||||
return function cleanUp() {
|
|
||||||
signal.removeEventListener("abort", listener);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function observedAttributes(instance, observedAttribute) {
|
|
||||||
const { observedAttributes: observedAttributes3 = [] } = instance.constructor;
|
|
||||||
return observedAttributes3.reduce(function(out, name) {
|
|
||||||
out[kebabToCamel(name)] = observedAttribute(instance, name);
|
|
||||||
return out;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
function kebabToCamel(name) {
|
|
||||||
return name.replace(/-./g, (x) => x[1].toUpperCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 = {
|
|
||||||
setDeleteAttr,
|
|
||||||
ssr: "",
|
|
||||||
D: globalThis.document,
|
|
||||||
F: globalThis.DocumentFragment,
|
|
||||||
H: globalThis.HTMLElement,
|
|
||||||
S: globalThis.SVGElement,
|
|
||||||
M: globalThis.MutationObserver,
|
|
||||||
q: (p) => p || Promise.resolve()
|
|
||||||
};
|
|
||||||
function setDeleteAttr(obj, prop, val) {
|
|
||||||
Reflect.set(obj, prop, val);
|
|
||||||
if (!isUndef(val)) return;
|
|
||||||
Reflect.deleteProperty(obj, prop);
|
|
||||||
if (isInstance(obj, enviroment.H) && obj.getAttribute(prop) === "undefined")
|
|
||||||
return obj.removeAttribute(prop);
|
|
||||||
if (Reflect.get(obj, prop) === "undefined")
|
|
||||||
return Reflect.set(obj, prop, "");
|
|
||||||
}
|
|
||||||
var keyLTE = "__dde_lifecyclesToEvents";
|
|
||||||
var evc = "dde:connected";
|
|
||||||
var evd = "dde:disconnected";
|
|
||||||
var eva = "dde:attributeChanged";
|
|
||||||
|
|
||||||
// src/dom.js
|
|
||||||
function queue(promise) {
|
|
||||||
return enviroment.q(promise);
|
|
||||||
}
|
|
||||||
var scopes = [{
|
|
||||||
get scope() {
|
|
||||||
return enviroment.D.body;
|
|
||||||
},
|
|
||||||
host: (c) => c ? c(enviroment.D.body) : enviroment.D.body,
|
|
||||||
prevent: true
|
|
||||||
}];
|
|
||||||
var 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;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
function append(...els) {
|
|
||||||
this.appendOriginal(...els);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
function chainableAppend(el) {
|
|
||||||
if (el.append === append) return el;
|
|
||||||
el.appendOriginal = el.append;
|
|
||||||
el.append = append;
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
var namespace;
|
|
||||||
function createElement(tag, attributes, ...addons) {
|
|
||||||
const s = signals(this);
|
|
||||||
let scoped = 0;
|
|
||||||
let el, el_host;
|
|
||||||
if (Object(attributes) !== attributes || s.isSignal(attributes))
|
|
||||||
attributes = { textContent: attributes };
|
|
||||||
switch (true) {
|
|
||||||
case typeof tag === "function": {
|
|
||||||
scoped = 1;
|
|
||||||
const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0);
|
|
||||||
scope.push({ scope: tag, host });
|
|
||||||
el = tag(attributes || void 0);
|
|
||||||
const is_fragment = isInstance(el, enviroment.F);
|
|
||||||
if (el.nodeName === "#comment") break;
|
|
||||||
const el_mark = createElement.mark({
|
|
||||||
type: "component",
|
|
||||||
name: tag.name,
|
|
||||||
host: is_fragment ? "this" : "parentElement"
|
|
||||||
});
|
|
||||||
el.prepend(el_mark);
|
|
||||||
if (is_fragment) el_host = el_mark;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case tag === "#text":
|
|
||||||
el = assign.call(this, enviroment.D.createTextNode(""), attributes);
|
|
||||||
break;
|
|
||||||
case (tag === "<>" || !tag):
|
|
||||||
el = assign.call(this, enviroment.D.createDocumentFragment(), attributes);
|
|
||||||
break;
|
|
||||||
case Boolean(namespace):
|
|
||||||
el = assign.call(this, enviroment.D.createElementNS(namespace, tag), attributes);
|
|
||||||
break;
|
|
||||||
case !el:
|
|
||||||
el = assign.call(this, enviroment.D.createElement(tag), attributes);
|
|
||||||
}
|
|
||||||
chainableAppend(el);
|
|
||||||
if (!el_host) el_host = el;
|
|
||||||
addons.forEach((c) => c(el_host));
|
|
||||||
if (scoped) scope.pop();
|
|
||||||
scoped = 2;
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
createElement.mark = function(attrs, is_open = false) {
|
|
||||||
attrs = Object.entries(attrs).map(([n, v]) => n + `="${v}"`).join(" ");
|
|
||||||
const end = is_open ? "" : "/";
|
|
||||||
const out = enviroment.D.createComment(`<dde:mark ${attrs}${enviroment.ssr}${end}>`);
|
|
||||||
if (is_open) out.end = enviroment.D.createComment("</dde:mark>");
|
|
||||||
return out;
|
|
||||||
};
|
|
||||||
function createElementNS(ns) {
|
|
||||||
const _this = this;
|
|
||||||
return function createElementNSCurried(...rest) {
|
|
||||||
namespace = ns;
|
|
||||||
const el = createElement.call(_this, ...rest);
|
|
||||||
namespace = void 0;
|
|
||||||
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 { setDeleteAttr: setDeleteAttr2 } = enviroment;
|
|
||||||
function assign(element, ...attributes) {
|
|
||||||
if (!attributes.length) return element;
|
|
||||||
assign_context.set(element, assignContext(element, this));
|
|
||||||
for (const [key, value] of Object.entries(oAssign({}, ...attributes)))
|
|
||||||
assignAttribute.call(this, element, key, value);
|
|
||||||
assign_context.delete(element);
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
function assignAttribute(element, key, value) {
|
|
||||||
const { setRemoveAttr, s } = assignContext(element, this);
|
|
||||||
const _this = this;
|
|
||||||
value = s.processReactiveAttribute(
|
|
||||||
element,
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
(key2, value2) => assignAttribute.call(_this, element, key2, value2)
|
|
||||||
);
|
|
||||||
const [k] = key;
|
|
||||||
if ("=" === k) return setRemoveAttr(key.slice(1), value);
|
|
||||||
if ("." === k) return setDelete(element, key.slice(1), value);
|
|
||||||
if (/(aria|data)([A-Z])/.test(key)) {
|
|
||||||
key = key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
||||||
return setRemoveAttr(key, value);
|
|
||||||
}
|
|
||||||
if ("className" === key) key = "class";
|
|
||||||
switch (key) {
|
|
||||||
case "xlink:href":
|
|
||||||
return setRemoveAttr(key, value, "http://www.w3.org/1999/xlink");
|
|
||||||
case "textContent":
|
|
||||||
return setDeleteAttr2(element, key, value);
|
|
||||||
case "style":
|
|
||||||
if (typeof value !== "object") break;
|
|
||||||
/* falls through */
|
|
||||||
case "dataset":
|
|
||||||
return forEachEntries(s, key, element, value, setDelete.bind(null, element[key]));
|
|
||||||
case "ariaset":
|
|
||||||
return forEachEntries(s, key, element, value, (key2, val) => setRemoveAttr("aria-" + key2, val));
|
|
||||||
case "classList":
|
|
||||||
return classListDeclarative.call(_this, element, value);
|
|
||||||
}
|
|
||||||
return isPropSetter(element, key) ? setDeleteAttr2(element, key, value) : setRemoveAttr(key, value);
|
|
||||||
}
|
|
||||||
function assignContext(element, _this) {
|
|
||||||
if (assign_context.has(element)) return assign_context.get(element);
|
|
||||||
const is_svg = isInstance(element, enviroment.S);
|
|
||||||
const setRemoveAttr = (is_svg ? setRemoveNS : setRemove).bind(null, element, "Attribute");
|
|
||||||
const s = signals(_this);
|
|
||||||
return { setRemoveAttr, s };
|
|
||||||
}
|
|
||||||
function classListDeclarative(element, toggle) {
|
|
||||||
const s = signals(this);
|
|
||||||
forEachEntries(
|
|
||||||
s,
|
|
||||||
"classList",
|
|
||||||
element,
|
|
||||||
toggle,
|
|
||||||
(class_name, val) => element.classList.toggle(class_name, val === -1 ? void 0 : Boolean(val))
|
|
||||||
);
|
|
||||||
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) {
|
|
||||||
if (!(key in el)) return false;
|
|
||||||
const des = getPropDescriptor(el, key);
|
|
||||||
return !isUndef(des.set);
|
|
||||||
}
|
|
||||||
function getPropDescriptor(p, key) {
|
|
||||||
p = Object.getPrototypeOf(p);
|
|
||||||
if (!p) return {};
|
|
||||||
const des = Object.getOwnPropertyDescriptor(p, key);
|
|
||||||
if (!des) return getPropDescriptor(p, key);
|
|
||||||
return des;
|
|
||||||
}
|
|
||||||
function forEachEntries(s, target, element, obj, cb) {
|
|
||||||
const S = String;
|
|
||||||
if (typeof obj !== "object" || obj === null) return;
|
|
||||||
return Object.entries(obj).forEach(function process([key, val]) {
|
|
||||||
if (!key) return;
|
|
||||||
key = new S(key);
|
|
||||||
key.target = target;
|
|
||||||
val = s.processReactiveAttribute(element, key, val, cb);
|
|
||||||
cb(key, val);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function setRemove(obj, prop, key, val) {
|
|
||||||
return obj[(isUndef(val) ? "remove" : "set") + prop](key, val);
|
|
||||||
}
|
|
||||||
function setRemoveNS(obj, prop, key, val, ns = null) {
|
|
||||||
return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val);
|
|
||||||
}
|
|
||||||
function setDelete(obj, key, val) {
|
|
||||||
Reflect.set(obj, key, val);
|
|
||||||
if (!isUndef(val)) return;
|
|
||||||
return Reflect.deleteProperty(obj, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
// src/events-observer.js
|
|
||||||
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
|
|
||||||
get() {
|
|
||||||
return () => {
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
function connectionsChangesObserverConstructor() {
|
|
||||||
const store = /* @__PURE__ */ new Map();
|
|
||||||
let is_observing = false;
|
|
||||||
const observerListener = (stop2) => function(mutations) {
|
|
||||||
for (const mutation of mutations) {
|
|
||||||
if (mutation.type !== "childList") continue;
|
|
||||||
if (observerAdded(mutation.addedNodes, true)) {
|
|
||||||
stop2();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (observerRemoved(mutation.removedNodes, true))
|
|
||||||
stop2();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const observer = new enviroment.M(observerListener(stop));
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* Creates an observer for a specific element
|
|
||||||
* @param {Element} element - Element to observe
|
|
||||||
* @returns {Function} Cleanup function
|
|
||||||
*/
|
|
||||||
observe(element) {
|
|
||||||
const o = new enviroment.M(observerListener(() => {
|
|
||||||
}));
|
|
||||||
o.observe(element, { childList: true, subtree: true });
|
|
||||||
return () => o.disconnect();
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Register a connection listener for an element
|
|
||||||
* @param {Element} element - Element to watch
|
|
||||||
* @param {Function} listener - Callback for connection event
|
|
||||||
*/
|
|
||||||
onConnected(element, listener) {
|
|
||||||
start();
|
|
||||||
const listeners = getElementStore(element);
|
|
||||||
if (listeners.connected.has(listener)) return;
|
|
||||||
listeners.connected.add(listener);
|
|
||||||
listeners.length_c += 1;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Unregister a connection listener
|
|
||||||
* @param {Element} element - Element being watched
|
|
||||||
* @param {Function} listener - Callback to remove
|
|
||||||
*/
|
|
||||||
offConnected(element, listener) {
|
|
||||||
if (!store.has(element)) return;
|
|
||||||
const ls = store.get(element);
|
|
||||||
if (!ls.connected.has(listener)) return;
|
|
||||||
ls.connected.delete(listener);
|
|
||||||
ls.length_c -= 1;
|
|
||||||
cleanWhenOff(element, ls);
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Register a disconnection listener for an element
|
|
||||||
* @param {Element} element - Element to watch
|
|
||||||
* @param {Function} listener - Callback for disconnection event
|
|
||||||
*/
|
|
||||||
onDisconnected(element, listener) {
|
|
||||||
start();
|
|
||||||
const listeners = getElementStore(element);
|
|
||||||
if (listeners.disconnected.has(listener)) return;
|
|
||||||
listeners.disconnected.add(listener);
|
|
||||||
listeners.length_d += 1;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Unregister a disconnection listener
|
|
||||||
* @param {Element} element - Element being watched
|
|
||||||
* @param {Function} listener - Callback to remove
|
|
||||||
*/
|
|
||||||
offDisconnected(element, listener) {
|
|
||||||
if (!store.has(element)) return;
|
|
||||||
const ls = store.get(element);
|
|
||||||
ls.disconnected.delete(listener);
|
|
||||||
ls.length_d -= 1;
|
|
||||||
cleanWhenOff(element, ls);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
function cleanWhenOff(element, ls) {
|
|
||||||
if (ls.length_c || ls.length_d)
|
|
||||||
return;
|
|
||||||
store.delete(element);
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
function getElementStore(element) {
|
|
||||||
if (store.has(element)) return store.get(element);
|
|
||||||
const out = {
|
|
||||||
connected: /* @__PURE__ */ new WeakSet(),
|
|
||||||
length_c: 0,
|
|
||||||
disconnected: /* @__PURE__ */ new WeakSet(),
|
|
||||||
length_d: 0
|
|
||||||
};
|
|
||||||
store.set(element, out);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
function start() {
|
|
||||||
if (is_observing) return;
|
|
||||||
is_observing = true;
|
|
||||||
observer.observe(enviroment.D.body, { childList: true, subtree: true });
|
|
||||||
}
|
|
||||||
function stop() {
|
|
||||||
if (!is_observing || store.size) return;
|
|
||||||
is_observing = false;
|
|
||||||
observer.disconnect();
|
|
||||||
}
|
|
||||||
function requestIdle() {
|
|
||||||
return new Promise(function(resolve) {
|
|
||||||
(requestIdleCallback || requestAnimationFrame)(resolve);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
async function collectChildren(element) {
|
|
||||||
if (store.size > 30)
|
|
||||||
await requestIdle();
|
|
||||||
const out = [];
|
|
||||||
if (!isInstance(element, Node)) return out;
|
|
||||||
for (const el of store.keys()) {
|
|
||||||
if (el === element || !isInstance(el, Node)) continue;
|
|
||||||
if (element.contains(el))
|
|
||||||
out.push(el);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
function observerAdded(addedNodes, is_root) {
|
|
||||||
let out = false;
|
|
||||||
for (const element of addedNodes) {
|
|
||||||
if (is_root) collectChildren(element).then(observerAdded);
|
|
||||||
if (!store.has(element)) continue;
|
|
||||||
const ls = store.get(element);
|
|
||||||
if (!ls.length_c) continue;
|
|
||||||
element.dispatchEvent(new Event(evc));
|
|
||||||
ls.connected = /* @__PURE__ */ new WeakSet();
|
|
||||||
ls.length_c = 0;
|
|
||||||
if (!ls.length_d) store.delete(element);
|
|
||||||
out = true;
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
function observerRemoved(removedNodes, is_root) {
|
|
||||||
let out = false;
|
|
||||||
for (const element of removedNodes) {
|
|
||||||
if (is_root) collectChildren(element).then(observerRemoved);
|
|
||||||
if (!store.has(element)) continue;
|
|
||||||
const ls = store.get(element);
|
|
||||||
if (!ls.length_d) continue;
|
|
||||||
(globalThis.queueMicrotask || setTimeout)(dispatchRemove(element));
|
|
||||||
out = true;
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
function dispatchRemove(element) {
|
|
||||||
return () => {
|
|
||||||
if (element.isConnected) return;
|
|
||||||
element.dispatchEvent(new Event(evd));
|
|
||||||
store.delete(element);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// src/customElement.js
|
|
||||||
function customElementRender(target, render, props = observedAttributes2) {
|
|
||||||
const custom_element = target.host || target;
|
|
||||||
scope.push({
|
|
||||||
scope: custom_element,
|
|
||||||
host: (...c) => c.length ? c.forEach((c2) => c2(custom_element)) : custom_element
|
|
||||||
});
|
|
||||||
if (typeof props === "function") props = props.call(custom_element, custom_element);
|
|
||||||
const is_lte = custom_element[keyLTE];
|
|
||||||
if (!is_lte) lifecyclesToEvents(custom_element);
|
|
||||||
const out = render.call(custom_element, props);
|
|
||||||
if (!is_lte) custom_element.dispatchEvent(new Event(evc));
|
|
||||||
if (target.nodeType === 11 && typeof target.mode === "string")
|
|
||||||
custom_element.addEventListener(evd, c_ch_o.observe(target), { once: true });
|
|
||||||
scope.pop();
|
|
||||||
return target.append(out);
|
|
||||||
}
|
|
||||||
function lifecyclesToEvents(class_declaration) {
|
|
||||||
wrapMethod(class_declaration.prototype, "connectedCallback", function(target, thisArg, detail) {
|
|
||||||
target.apply(thisArg, detail);
|
|
||||||
thisArg.dispatchEvent(new Event(evc));
|
|
||||||
});
|
|
||||||
wrapMethod(class_declaration.prototype, "disconnectedCallback", function(target, thisArg, detail) {
|
|
||||||
target.apply(thisArg, detail);
|
|
||||||
(globalThis.queueMicrotask || setTimeout)(
|
|
||||||
() => !thisArg.isConnected && thisArg.dispatchEvent(new Event(evd))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
wrapMethod(class_declaration.prototype, "attributeChangedCallback", function(target, thisArg, detail) {
|
|
||||||
const [attribute, , value] = detail;
|
|
||||||
thisArg.dispatchEvent(new CustomEvent(eva, {
|
|
||||||
detail: [attribute, value]
|
|
||||||
}));
|
|
||||||
target.apply(thisArg, detail);
|
|
||||||
});
|
|
||||||
class_declaration.prototype[keyLTE] = true;
|
|
||||||
return class_declaration;
|
|
||||||
}
|
|
||||||
function wrapMethod(obj, method, apply) {
|
|
||||||
obj[method] = new Proxy(obj[method] || (() => {
|
|
||||||
}), { apply });
|
|
||||||
}
|
|
||||||
function observedAttributes2(instance) {
|
|
||||||
return observedAttributes(instance, (i, n) => i.getAttribute(n));
|
|
||||||
}
|
|
||||||
|
|
||||||
// src/events.js
|
|
||||||
function dispatchEvent(name, options, host) {
|
|
||||||
if (typeof options === "function") {
|
|
||||||
host = options;
|
|
||||||
options = null;
|
|
||||||
}
|
|
||||||
if (!options) options = {};
|
|
||||||
return function dispatch(element, ...d) {
|
|
||||||
if (host) {
|
|
||||||
d.unshift(element);
|
|
||||||
element = typeof host === "function" ? host() : host;
|
|
||||||
}
|
|
||||||
const event = d.length ? new CustomEvent(name, oAssign({ detail: d[0] }, options)) : new Event(name, options);
|
|
||||||
return element.dispatchEvent(event);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function on(event, listener, options) {
|
|
||||||
return function registerElement(element) {
|
|
||||||
element.addEventListener(event, listener, options);
|
|
||||||
return element;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true });
|
|
||||||
on.connected = function(listener, options) {
|
|
||||||
options = lifeOptions(options);
|
|
||||||
return function registerElement(element) {
|
|
||||||
element.addEventListener(evc, listener, options);
|
|
||||||
if (element[keyLTE]) return element;
|
|
||||||
if (element.isConnected) return element.dispatchEvent(new Event(evc)), element;
|
|
||||||
const c = onAbort(options.signal, () => c_ch_o.offConnected(element, listener));
|
|
||||||
if (c) c_ch_o.onConnected(element, listener);
|
|
||||||
return element;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
on.disconnected = function(listener, options) {
|
|
||||||
options = lifeOptions(options);
|
|
||||||
return function registerElement(element) {
|
|
||||||
element.addEventListener(evd, listener, options);
|
|
||||||
if (element[keyLTE]) return element;
|
|
||||||
const c = onAbort(options.signal, () => c_ch_o.offDisconnected(element, listener));
|
|
||||||
if (c) c_ch_o.onDisconnected(element, listener);
|
|
||||||
return element;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
var store_abort = /* @__PURE__ */ new WeakMap();
|
|
||||||
on.disconnectedAsAbort = function(host) {
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
var els_attribute_store = /* @__PURE__ */ new WeakSet();
|
|
||||||
on.attributeChanged = function(listener, options) {
|
|
||||||
if (typeof options !== "object")
|
|
||||||
options = {};
|
|
||||||
return function registerElement(element) {
|
|
||||||
element.addEventListener(eva, listener, options);
|
|
||||||
if (element[keyLTE] || els_attribute_store.has(element))
|
|
||||||
return element;
|
|
||||||
if (!enviroment.M) return element;
|
|
||||||
const observer = new enviroment.M(function(mutations) {
|
|
||||||
for (const { attributeName, target } of mutations)
|
|
||||||
target.dispatchEvent(
|
|
||||||
new CustomEvent(eva, { detail: [attributeName, target.getAttribute(attributeName)] })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
const c = onAbort(options.signal, () => observer.disconnect());
|
|
||||||
if (c) observer.observe(element, { attributes: true });
|
|
||||||
return element;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
globalThis.dde= {
|
|
||||||
assign,
|
|
||||||
assignAttribute,
|
|
||||||
chainableAppend,
|
|
||||||
classListDeclarative,
|
|
||||||
createElement,
|
|
||||||
createElementNS,
|
|
||||||
customElementRender,
|
|
||||||
customElementWithDDE: lifecyclesToEvents,
|
|
||||||
dispatchEvent,
|
|
||||||
el: createElement,
|
|
||||||
elNS: createElementNS,
|
|
||||||
elementAttribute,
|
|
||||||
lifecyclesToEvents,
|
|
||||||
observedAttributes: observedAttributes2,
|
|
||||||
on,
|
|
||||||
queue,
|
|
||||||
registerReactivity,
|
|
||||||
scope,
|
|
||||||
simulateSlots
|
|
||||||
};
|
|
||||||
|
|
||||||
})();
|
|
25
dist/dde.min.js
vendored
25
dist/dde.min.js
vendored
File diff suppressed because one or more lines are too long
641
dist/esm-with-signals.d.min.ts
vendored
641
dist/esm-with-signals.d.min.ts
vendored
@ -1,641 +0,0 @@
|
|||||||
declare global{ /* ddeSignal */ }
|
|
||||||
type CustomElementTagNameMap= { '#text': Text, '#comment': Comment }
|
|
||||||
type SupportedElement=
|
|
||||||
HTMLElementTagNameMap[keyof HTMLElementTagNameMap]
|
|
||||||
| SVGElementTagNameMap[keyof SVGElementTagNameMap]
|
|
||||||
| MathMLElementTagNameMap[keyof MathMLElementTagNameMap]
|
|
||||||
| CustomElementTagNameMap[keyof CustomElementTagNameMap]
|
|
||||||
declare global {
|
|
||||||
type ddeComponentAttributes= Record<any, any> | undefined;
|
|
||||||
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node>= (element: El)=> any;
|
|
||||||
type ddeString= string | ddeSignal<string>
|
|
||||||
type ddeStringable= ddeString | number | ddeSignal<number>
|
|
||||||
}
|
|
||||||
type PascalCase=
|
|
||||||
`${Capitalize<string>}${string}`;
|
|
||||||
type AttrsModified= {
|
|
||||||
/**
|
|
||||||
* Use string like in HTML (internally uses `*.setAttribute("style", *)`), or object representation (like DOM API).
|
|
||||||
*/
|
|
||||||
style: Partial<CSSStyleDeclaration> | ddeString
|
|
||||||
| Partial<{ [K in keyof CSSStyleDeclaration]: ddeSignal<CSSStyleDeclaration[K]> }>
|
|
||||||
/**
|
|
||||||
* Provide option to add/remove/toggle CSS clasess (index of object) using 1/0/-1.
|
|
||||||
* In fact `el.classList.toggle(class_name)` for `-1` and `el.classList.toggle(class_name, Boolean(...))`
|
|
||||||
* for others.
|
|
||||||
*/
|
|
||||||
classList: Record<string,-1|0|1|boolean|ddeSignal<-1|0|1|boolean>>,
|
|
||||||
/**
|
|
||||||
* Used by the dataset HTML attribute to represent data for custom attributes added to elements.
|
|
||||||
* Values are converted to string (see {@link DOMStringMap}).
|
|
||||||
*
|
|
||||||
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMStringMap)
|
|
||||||
* */
|
|
||||||
dataset: Record<string, ddeStringable>,
|
|
||||||
/**
|
|
||||||
* Sets `aria-*` simiraly to `dataset`
|
|
||||||
* */
|
|
||||||
ariaset: Record<string, ddeString>,
|
|
||||||
} & Record<`=${string}` | `data${PascalCase}` | `aria${PascalCase}`, ddeString>
|
|
||||||
& Record<`.${string}`, any>
|
|
||||||
type _fromElsInterfaces<EL extends SupportedElement>= Omit<EL, keyof AttrsModified>;
|
|
||||||
type IsReadonly<T, K extends keyof T> =
|
|
||||||
T extends { readonly [P in K]: T[K] } ? true : false;
|
|
||||||
/**
|
|
||||||
* Just element attributtes
|
|
||||||
*
|
|
||||||
* In most cases, you can use native propertie such as
|
|
||||||
* [MDN WEB/API/Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) and so on
|
|
||||||
* (e.g. [`Text`](https://developer.mozilla.org/en-US/docs/Web/API/Text)).
|
|
||||||
*
|
|
||||||
* There is added support for `data[A-Z].*`/`aria[A-Z].*` to be converted to the kebab-case alternatives.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
type ElementAttributes<T extends SupportedElement>= Partial<{
|
|
||||||
[K in keyof _fromElsInterfaces<T>]:
|
|
||||||
_fromElsInterfaces<T>[K] extends ((...p: any[])=> any)
|
|
||||||
? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>)=>
|
|
||||||
ddeSignal<ReturnType<_fromElsInterfaces<T>[K]>>)
|
|
||||||
: (IsReadonly<_fromElsInterfaces<T>, K> extends false
|
|
||||||
? _fromElsInterfaces<T>[K] | ddeSignal<_fromElsInterfaces<T>[K]>
|
|
||||||
: ddeStringable)
|
|
||||||
} & AttrsModified> & Record<string, any>;
|
|
||||||
export function classListDeclarative<El extends SupportedElement>(
|
|
||||||
element: El,
|
|
||||||
classList: AttrsModified["classList"]
|
|
||||||
): El
|
|
||||||
export function assign<El extends SupportedElement>(element: El, ...attrs_array: ElementAttributes<El>[]): El
|
|
||||||
export function assignAttribute<El extends SupportedElement, ATT extends keyof ElementAttributes<El>>(
|
|
||||||
element: El,
|
|
||||||
attr: ATT,
|
|
||||||
value: ElementAttributes<El>[ATT]
|
|
||||||
): ElementAttributes<El>[ATT]
|
|
||||||
|
|
||||||
type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap;
|
|
||||||
export namespace el {
|
|
||||||
/**
|
|
||||||
* Creates a marker comment for elements
|
|
||||||
*
|
|
||||||
* @param attrs - Marker attributes
|
|
||||||
* @param [is_open=false] - Whether the marker is open-ended
|
|
||||||
* @returns Comment node marker
|
|
||||||
*/
|
|
||||||
export function mark(
|
|
||||||
attrs: { type: "component"|"reactive"|"later", name?: string, host?: "this"|"parentElement" },
|
|
||||||
is_open?: boolean
|
|
||||||
): Comment;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function el<
|
|
||||||
A extends ddeComponentAttributes,
|
|
||||||
EL extends SupportedElement | ddeDocumentFragment
|
|
||||||
>(
|
|
||||||
component: (attr: A, ...rest: any[])=> EL,
|
|
||||||
attrs?: NoInfer<A>,
|
|
||||||
...addons: ddeElementAddon<EL>[]
|
|
||||||
): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
|
|
||||||
? EL
|
|
||||||
: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement )
|
|
||||||
export function el<
|
|
||||||
A extends { textContent: ddeStringable },
|
|
||||||
EL extends SupportedElement | ddeDocumentFragment
|
|
||||||
>(
|
|
||||||
component: (attr: A, ...rest: any[])=> EL,
|
|
||||||
attrs?: NoInfer<A>["textContent"],
|
|
||||||
...addons: ddeElementAddon<EL>[]
|
|
||||||
): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
|
|
||||||
? EL
|
|
||||||
: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement )
|
|
||||||
export function el<
|
|
||||||
TAG extends keyof ExtendedHTMLElementTagNameMap,
|
|
||||||
>(
|
|
||||||
tag_name: TAG,
|
|
||||||
attrs?: ElementAttributes<ExtendedHTMLElementTagNameMap[NoInfer<TAG>]> | ddeStringable,
|
|
||||||
...addons: ddeElementAddon<
|
|
||||||
ExtendedHTMLElementTagNameMap[NoInfer<TAG>]
|
|
||||||
>[], // TODO: for now addons must have the same element
|
|
||||||
): TAG extends keyof ddeHTMLElementTagNameMap ? ddeHTMLElementTagNameMap[TAG] : ddeHTMLElement
|
|
||||||
export function el(
|
|
||||||
tag_name?: "<>",
|
|
||||||
): ddeDocumentFragment
|
|
||||||
export function el(
|
|
||||||
tag_name: string,
|
|
||||||
attrs?: ElementAttributes<HTMLElement> | ddeStringable,
|
|
||||||
...addons: ddeElementAddon<HTMLElement>[]
|
|
||||||
): ddeHTMLElement
|
|
||||||
export { el as createElement }
|
|
||||||
|
|
||||||
export function elNS(
|
|
||||||
namespace: "http://www.w3.org/2000/svg"
|
|
||||||
): <
|
|
||||||
TAG extends keyof SVGElementTagNameMap & string,
|
|
||||||
EL extends ( TAG extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[TAG] : SVGElement ),
|
|
||||||
>(
|
|
||||||
tag_name: TAG,
|
|
||||||
attrs?: ElementAttributes<NoInfer<EL>> | ddeStringable,
|
|
||||||
...addons: ddeElementAddon<NoInfer<EL>>[]
|
|
||||||
)=> TAG extends keyof ddeSVGElementTagNameMap ? ddeSVGElementTagNameMap[TAG] : ddeSVGElement
|
|
||||||
export function elNS(
|
|
||||||
namespace: "http://www.w3.org/1998/Math/MathML"
|
|
||||||
): <
|
|
||||||
TAG extends keyof MathMLElementTagNameMap & string,
|
|
||||||
EL extends ( TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement ),
|
|
||||||
>(
|
|
||||||
tag_name: TAG,
|
|
||||||
attrs?: ddeStringable | Partial<{
|
|
||||||
[key in keyof EL]: EL[key] | ddeSignal<EL[key]> | string | number | boolean
|
|
||||||
}>,
|
|
||||||
...addons: ddeElementAddon<NoInfer<EL>>[]
|
|
||||||
)=> ddeMathMLElement
|
|
||||||
export function elNS(
|
|
||||||
namespace: string
|
|
||||||
): (
|
|
||||||
tag_name: string,
|
|
||||||
attrs?: string | ddeStringable | Record<string, any>,
|
|
||||||
...addons: ddeElementAddon<SupportedElement>[]
|
|
||||||
)=> SupportedElement
|
|
||||||
export { elNS as createElementNS }
|
|
||||||
|
|
||||||
export function chainableAppend<EL extends SupportedElement>(el: EL): EL;
|
|
||||||
/** Simulate slots for ddeComponents */
|
|
||||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(
|
|
||||||
root: EL,
|
|
||||||
): EL
|
|
||||||
/**
|
|
||||||
* Simulate slots in Custom Elements without using `shadowRoot`.
|
|
||||||
* @param el Custom Element root element
|
|
||||||
* @param body Body of the custom element
|
|
||||||
* */
|
|
||||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(
|
|
||||||
el: HTMLElement,
|
|
||||||
body: EL,
|
|
||||||
): EL
|
|
||||||
|
|
||||||
export function dispatchEvent(name: keyof DocumentEventMap | string, element: SupportedElement):
|
|
||||||
(data?: any)=> void;
|
|
||||||
export function dispatchEvent(name: keyof DocumentEventMap | string, options?: EventInit):
|
|
||||||
(element: SupportedElement, data?: any)=> void;
|
|
||||||
export function dispatchEvent(
|
|
||||||
name: keyof DocumentEventMap | string,
|
|
||||||
options: EventInit | null,
|
|
||||||
element: SupportedElement | (()=> SupportedElement)
|
|
||||||
): (data?: any)=> void;
|
|
||||||
interface On{
|
|
||||||
/** Listens to the DOM event. See {@link Document.addEventListener} */
|
|
||||||
<
|
|
||||||
Event extends keyof DocumentEventMap,
|
|
||||||
EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>,
|
|
||||||
>(
|
|
||||||
type: Event,
|
|
||||||
listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any,
|
|
||||||
options?: AddEventListenerOptions
|
|
||||||
) : EE;
|
|
||||||
<
|
|
||||||
EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>,
|
|
||||||
>(
|
|
||||||
type: string,
|
|
||||||
listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent ) => any,
|
|
||||||
options?: AddEventListenerOptions
|
|
||||||
) : EE;
|
|
||||||
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
|
|
||||||
connected<
|
|
||||||
EE extends ddeElementAddon<SupportedElement>,
|
|
||||||
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
|
|
||||||
>(
|
|
||||||
listener: (this: El, event: CustomEvent<El>) => any,
|
|
||||||
options?: AddEventListenerOptions
|
|
||||||
) : EE;
|
|
||||||
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
|
|
||||||
disconnected<
|
|
||||||
EE extends ddeElementAddon<SupportedElement>,
|
|
||||||
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
|
|
||||||
>(
|
|
||||||
listener: (this: El, event: CustomEvent<void>) => any,
|
|
||||||
options?: AddEventListenerOptions
|
|
||||||
) : EE;
|
|
||||||
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
|
|
||||||
attributeChanged<
|
|
||||||
EE extends ddeElementAddon<SupportedElement>,
|
|
||||||
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
|
|
||||||
>(
|
|
||||||
listener: (this: El, event: CustomEvent<[ string, string ]>) => any,
|
|
||||||
options?: AddEventListenerOptions
|
|
||||||
) : EE;
|
|
||||||
}
|
|
||||||
export const on: On;
|
|
||||||
|
|
||||||
type Scope= {
|
|
||||||
scope: Node | Function | Object,
|
|
||||||
host: ddeElementAddon<any>,
|
|
||||||
custom_element: false | HTMLElement,
|
|
||||||
prevent: boolean
|
|
||||||
};
|
|
||||||
/** Current scope created last time the `el(Function)` was invoke. (Or {@link scope.push}) */
|
|
||||||
export const scope: {
|
|
||||||
current: Scope,
|
|
||||||
/** Stops all automatizations. E. g. signals used as attributes in current scope
|
|
||||||
* registers removing these listeners (and clean signal if no other listeners are detected)
|
|
||||||
* on `disconnected` event. */
|
|
||||||
preventDefault<T extends boolean>(prevent: T): T,
|
|
||||||
/**
|
|
||||||
* This represents reference to the current host element — `scope.host()`.
|
|
||||||
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
|
||||||
* — `scope.host(on.connected(console.log))`.
|
|
||||||
* */
|
|
||||||
host: (...addons: ddeElementAddon<SupportedElement>[])=> HTMLElement,
|
|
||||||
|
|
||||||
state: Scope[],
|
|
||||||
/** Adds new child scope. All attributes are inherited by default. */
|
|
||||||
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>,
|
|
||||||
/** Adds root scope as a child of the current scope. */
|
|
||||||
pushRoot(): ReturnType<Array<Scope>["push"]>,
|
|
||||||
/** Removes last/current child scope. */
|
|
||||||
pop(): ReturnType<Array<Scope>["pop"]>,
|
|
||||||
};
|
|
||||||
|
|
||||||
export function customElementRender<
|
|
||||||
EL extends HTMLElement,
|
|
||||||
P extends any = Record<string, string | ddeSignal<string>>
|
|
||||||
>(
|
|
||||||
target: ShadowRoot | EL,
|
|
||||||
render: (props: P)=> SupportedElement | DocumentFragment,
|
|
||||||
props?: P | ((el: EL)=> P)
|
|
||||||
): EL
|
|
||||||
export function customElementWithDDE<EL extends (new ()=> HTMLElement)>(custom_element: EL): EL
|
|
||||||
export function lifecyclesToEvents<EL extends (new ()=> HTMLElement)>(custom_element: EL): EL
|
|
||||||
export function observedAttributes(custom_element: HTMLElement): Record<string, string>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is used primarly for server side rendering. To be sure that all async operations
|
|
||||||
* are finished before the page is sent to the client.
|
|
||||||
* ```
|
|
||||||
* // on component
|
|
||||||
* function component(){
|
|
||||||
* …
|
|
||||||
* queue(fetch(...).then(...));
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* // building the page
|
|
||||||
* async function build(){
|
|
||||||
* const { component }= await import("./component.js");
|
|
||||||
* document.body.append(el(component));
|
|
||||||
* await queue();
|
|
||||||
* retutn document.body.innerHTML;
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
* */
|
|
||||||
export function queue(promise?: Promise<unknown>): Promise<unknown>;
|
|
||||||
|
|
||||||
/* TypeScript MEH */
|
|
||||||
declare global{
|
|
||||||
type ddeAppend<el>= (...nodes: (Node | string)[])=> el;
|
|
||||||
|
|
||||||
interface ddeDocumentFragment extends DocumentFragment{ append: ddeAppend<ddeDocumentFragment>; }
|
|
||||||
interface ddeHTMLElement extends HTMLElement{ append: ddeAppend<ddeHTMLElement>; }
|
|
||||||
interface ddeSVGElement extends SVGElement{ append: ddeAppend<ddeSVGElement>; }
|
|
||||||
interface ddeMathMLElement extends MathMLElement{ append: ddeAppend<ddeMathMLElement>; }
|
|
||||||
|
|
||||||
interface ddeHTMLElementTagNameMap {
|
|
||||||
"a": ddeHTMLAnchorElement;
|
|
||||||
"area": ddeHTMLAreaElement;
|
|
||||||
"audio": ddeHTMLAudioElement;
|
|
||||||
"base": ddeHTMLBaseElement;
|
|
||||||
"blockquote": ddeHTMLQuoteElement;
|
|
||||||
"body": ddeHTMLBodyElement;
|
|
||||||
"br": ddeHTMLBRElement;
|
|
||||||
"button": ddeHTMLButtonElement;
|
|
||||||
"canvas": ddeHTMLCanvasElement;
|
|
||||||
"caption": ddeHTMLTableCaptionElement;
|
|
||||||
"col": ddeHTMLTableColElement;
|
|
||||||
"colgroup": ddeHTMLTableColElement;
|
|
||||||
"data": ddeHTMLDataElement;
|
|
||||||
"datalist": ddeHTMLDataListElement;
|
|
||||||
"del": ddeHTMLModElement;
|
|
||||||
"details": ddeHTMLDetailsElement;
|
|
||||||
"dialog": ddeHTMLDialogElement;
|
|
||||||
"div": ddeHTMLDivElement;
|
|
||||||
"dl": ddeHTMLDListElement;
|
|
||||||
"embed": ddeHTMLEmbedElement;
|
|
||||||
"fieldset": ddeHTMLFieldSetElement;
|
|
||||||
"form": ddeHTMLFormElement;
|
|
||||||
"h1": ddeHTMLHeadingElement;
|
|
||||||
"h2": ddeHTMLHeadingElement;
|
|
||||||
"h3": ddeHTMLHeadingElement;
|
|
||||||
"h4": ddeHTMLHeadingElement;
|
|
||||||
"h5": ddeHTMLHeadingElement;
|
|
||||||
"h6": ddeHTMLHeadingElement;
|
|
||||||
"head": ddeHTMLHeadElement;
|
|
||||||
"hr": ddeHTMLHRElement;
|
|
||||||
"html": ddeHTMLHtmlElement;
|
|
||||||
"iframe": ddeHTMLIFrameElement;
|
|
||||||
"img": ddeHTMLImageElement;
|
|
||||||
"input": ddeHTMLInputElement;
|
|
||||||
"ins": ddeHTMLModElement;
|
|
||||||
"label": ddeHTMLLabelElement;
|
|
||||||
"legend": ddeHTMLLegendElement;
|
|
||||||
"li": ddeHTMLLIElement;
|
|
||||||
"link": ddeHTMLLinkElement;
|
|
||||||
"map": ddeHTMLMapElement;
|
|
||||||
"menu": ddeHTMLMenuElement;
|
|
||||||
"meta": ddeHTMLMetaElement;
|
|
||||||
"meter": ddeHTMLMeterElement;
|
|
||||||
"object": ddeHTMLObjectElement;
|
|
||||||
"ol": ddeHTMLOListElement;
|
|
||||||
"optgroup": ddeHTMLOptGroupElement;
|
|
||||||
"option": ddeHTMLOptionElement;
|
|
||||||
"output": ddeHTMLOutputElement;
|
|
||||||
"p": ddeHTMLParagraphElement;
|
|
||||||
"picture": ddeHTMLPictureElement;
|
|
||||||
"pre": ddeHTMLPreElement;
|
|
||||||
"progress": ddeHTMLProgressElement;
|
|
||||||
"q": ddeHTMLQuoteElement;
|
|
||||||
"script": ddeHTMLScriptElement;
|
|
||||||
"select": ddeHTMLSelectElement;
|
|
||||||
"slot": ddeHTMLSlotElement;
|
|
||||||
"source": ddeHTMLSourceElement;
|
|
||||||
"span": ddeHTMLSpanElement;
|
|
||||||
"style": ddeHTMLStyleElement;
|
|
||||||
"table": ddeHTMLTableElement;
|
|
||||||
"tbody": ddeHTMLTableSectionElement;
|
|
||||||
"td": ddeHTMLTableCellElement;
|
|
||||||
"template": ddeHTMLTemplateElement;
|
|
||||||
"textarea": ddeHTMLTextAreaElement;
|
|
||||||
"tfoot": ddeHTMLTableSectionElement;
|
|
||||||
"th": ddeHTMLTableCellElement;
|
|
||||||
"thead": ddeHTMLTableSectionElement;
|
|
||||||
"time": ddeHTMLTimeElement;
|
|
||||||
"title": ddeHTMLTitleElement;
|
|
||||||
"tr": ddeHTMLTableRowElement;
|
|
||||||
"track": ddeHTMLTrackElement;
|
|
||||||
"ul": ddeHTMLUListElement;
|
|
||||||
"video": ddeHTMLVideoElement;
|
|
||||||
}
|
|
||||||
interface ddeSVGElementTagNameMap {
|
|
||||||
"a": ddeSVGAElement;
|
|
||||||
"animate": ddeSVGAnimateElement;
|
|
||||||
"animateMotion": ddeSVGAnimateMotionElement;
|
|
||||||
"animateTransform": ddeSVGAnimateTransformElement;
|
|
||||||
"circle": ddeSVGCircleElement;
|
|
||||||
"clipPath": ddeSVGClipPathElement;
|
|
||||||
"defs": ddeSVGDefsElement;
|
|
||||||
"desc": ddeSVGDescElement;
|
|
||||||
"ellipse": ddeSVGEllipseElement;
|
|
||||||
"feBlend": ddeSVGFEBlendElement;
|
|
||||||
"feColorMatrix": ddeSVGFEColorMatrixElement;
|
|
||||||
"feComponentTransfer": ddeSVGFEComponentTransferElement;
|
|
||||||
"feComposite": ddeSVGFECompositeElement;
|
|
||||||
"feConvolveMatrix": ddeSVGFEConvolveMatrixElement;
|
|
||||||
"feDiffuseLighting": ddeSVGFEDiffuseLightingElement;
|
|
||||||
"feDisplacementMap": ddeSVGFEDisplacementMapElement;
|
|
||||||
"feDistantLight": ddeSVGFEDistantLightElement;
|
|
||||||
"feDropShadow": ddeSVGFEDropShadowElement;
|
|
||||||
"feFlood": ddeSVGFEFloodElement;
|
|
||||||
"feFuncA": ddeSVGFEFuncAElement;
|
|
||||||
"feFuncB": ddeSVGFEFuncBElement;
|
|
||||||
"feFuncG": ddeSVGFEFuncGElement;
|
|
||||||
"feFuncR": ddeSVGFEFuncRElement;
|
|
||||||
"feGaussianBlur": ddeSVGFEGaussianBlurElement;
|
|
||||||
"feImage": ddeSVGFEImageElement;
|
|
||||||
"feMerge": ddeSVGFEMergeElement;
|
|
||||||
"feMergeNode": ddeSVGFEMergeNodeElement;
|
|
||||||
"feMorphology": ddeSVGFEMorphologyElement;
|
|
||||||
"feOffset": ddeSVGFEOffsetElement;
|
|
||||||
"fePointLight": ddeSVGFEPointLightElement;
|
|
||||||
"feSpecularLighting": ddeSVGFESpecularLightingElement;
|
|
||||||
"feSpotLight": ddeSVGFESpotLightElement;
|
|
||||||
"feTile": ddeSVGFETileElement;
|
|
||||||
"feTurbulence": ddeSVGFETurbulenceElement;
|
|
||||||
"filter": ddeSVGFilterElement;
|
|
||||||
"foreignObject": ddeSVGForeignObjectElement;
|
|
||||||
"g": ddeSVGGElement;
|
|
||||||
"image": ddeSVGImageElement;
|
|
||||||
"line": ddeSVGLineElement;
|
|
||||||
"linearGradient": ddeSVGLinearGradientElement;
|
|
||||||
"marker": ddeSVGMarkerElement;
|
|
||||||
"mask": ddeSVGMaskElement;
|
|
||||||
"metadata": ddeSVGMetadataElement;
|
|
||||||
"mpath": ddeSVGMPathElement;
|
|
||||||
"path": ddeSVGPathElement;
|
|
||||||
"pattern": ddeSVGPatternElement;
|
|
||||||
"polygon": ddeSVGPolygonElement;
|
|
||||||
"polyline": ddeSVGPolylineElement;
|
|
||||||
"radialGradient": ddeSVGRadialGradientElement;
|
|
||||||
"rect": ddeSVGRectElement;
|
|
||||||
"script": ddeSVGScriptElement;
|
|
||||||
"set": ddeSVGSetElement;
|
|
||||||
"stop": ddeSVGStopElement;
|
|
||||||
"style": ddeSVGStyleElement;
|
|
||||||
"svg": ddeSVGSVGElement;
|
|
||||||
"switch": ddeSVGSwitchElement;
|
|
||||||
"symbol": ddeSVGSymbolElement;
|
|
||||||
"text": ddeSVGTextElement;
|
|
||||||
"textPath": ddeSVGTextPathElement;
|
|
||||||
"title": ddeSVGTitleElement;
|
|
||||||
"tspan": ddeSVGTSpanElement;
|
|
||||||
"use": ddeSVGUseElement;
|
|
||||||
"view": ddeSVGViewElement;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// editorconfig-checker-disable
|
|
||||||
interface ddeHTMLAnchorElement extends HTMLAnchorElement{ append: ddeAppend<ddeHTMLAnchorElement>; }
|
|
||||||
interface ddeHTMLAreaElement extends HTMLAreaElement{ append: ddeAppend<ddeHTMLAreaElement>; }
|
|
||||||
interface ddeHTMLAudioElement extends HTMLAudioElement{ append: ddeAppend<ddeHTMLAudioElement>; }
|
|
||||||
interface ddeHTMLBaseElement extends HTMLBaseElement{ append: ddeAppend<ddeHTMLBaseElement>; }
|
|
||||||
interface ddeHTMLQuoteElement extends HTMLQuoteElement{ append: ddeAppend<ddeHTMLQuoteElement>; }
|
|
||||||
interface ddeHTMLBodyElement extends HTMLBodyElement{ append: ddeAppend<ddeHTMLBodyElement>; }
|
|
||||||
interface ddeHTMLBRElement extends HTMLBRElement{ append: ddeAppend<ddeHTMLBRElement>; }
|
|
||||||
interface ddeHTMLButtonElement extends HTMLButtonElement{ append: ddeAppend<ddeHTMLButtonElement>; }
|
|
||||||
interface ddeHTMLCanvasElement extends HTMLCanvasElement{ append: ddeAppend<ddeHTMLCanvasElement>; }
|
|
||||||
interface ddeHTMLTableCaptionElement extends HTMLTableCaptionElement{ append: ddeAppend<ddeHTMLTableCaptionElement>; }
|
|
||||||
interface ddeHTMLTableColElement extends HTMLTableColElement{ append: ddeAppend<ddeHTMLTableColElement>; }
|
|
||||||
interface ddeHTMLTableColElement extends HTMLTableColElement{ append: ddeAppend<ddeHTMLTableColElement>; }
|
|
||||||
interface ddeHTMLDataElement extends HTMLDataElement{ append: ddeAppend<ddeHTMLDataElement>; }
|
|
||||||
interface ddeHTMLDataListElement extends HTMLDataListElement{ append: ddeAppend<ddeHTMLDataListElement>; }
|
|
||||||
interface ddeHTMLModElement extends HTMLModElement{ append: ddeAppend<ddeHTMLModElement>; }
|
|
||||||
interface ddeHTMLDetailsElement extends HTMLDetailsElement{ append: ddeAppend<ddeHTMLDetailsElement>; }
|
|
||||||
interface ddeHTMLDialogElement extends HTMLDialogElement{ append: ddeAppend<ddeHTMLDialogElement>; }
|
|
||||||
interface ddeHTMLDivElement extends HTMLDivElement{ append: ddeAppend<ddeHTMLDivElement>; }
|
|
||||||
interface ddeHTMLDListElement extends HTMLDListElement{ append: ddeAppend<ddeHTMLDListElement>; }
|
|
||||||
interface ddeHTMLEmbedElement extends HTMLEmbedElement{ append: ddeAppend<ddeHTMLEmbedElement>; }
|
|
||||||
interface ddeHTMLFieldSetElement extends HTMLFieldSetElement{ append: ddeAppend<ddeHTMLFieldSetElement>; }
|
|
||||||
interface ddeHTMLFormElement extends HTMLFormElement{ append: ddeAppend<ddeHTMLFormElement>; }
|
|
||||||
interface ddeHTMLHeadingElement extends HTMLHeadingElement{ append: ddeAppend<ddeHTMLHeadingElement>; }
|
|
||||||
interface ddeHTMLHeadElement extends HTMLHeadElement{ append: ddeAppend<ddeHTMLHeadElement>; }
|
|
||||||
interface ddeHTMLHRElement extends HTMLHRElement{ append: ddeAppend<ddeHTMLHRElement>; }
|
|
||||||
interface ddeHTMLHtmlElement extends HTMLHtmlElement{ append: ddeAppend<ddeHTMLHtmlElement>; }
|
|
||||||
interface ddeHTMLIFrameElement extends HTMLIFrameElement{ append: ddeAppend<ddeHTMLIFrameElement>; }
|
|
||||||
interface ddeHTMLImageElement extends HTMLImageElement{ append: ddeAppend<ddeHTMLImageElement>; }
|
|
||||||
interface ddeHTMLInputElement extends HTMLInputElement{ append: ddeAppend<ddeHTMLInputElement>; }
|
|
||||||
interface ddeHTMLLabelElement extends HTMLLabelElement{ append: ddeAppend<ddeHTMLLabelElement>; }
|
|
||||||
interface ddeHTMLLegendElement extends HTMLLegendElement{ append: ddeAppend<ddeHTMLLegendElement>; }
|
|
||||||
interface ddeHTMLLIElement extends HTMLLIElement{ append: ddeAppend<ddeHTMLLIElement>; }
|
|
||||||
interface ddeHTMLLinkElement extends HTMLLinkElement{ append: ddeAppend<ddeHTMLLinkElement>; }
|
|
||||||
interface ddeHTMLMapElement extends HTMLMapElement{ append: ddeAppend<ddeHTMLMapElement>; }
|
|
||||||
interface ddeHTMLMenuElement extends HTMLMenuElement{ append: ddeAppend<ddeHTMLMenuElement>; }
|
|
||||||
interface ddeHTMLMetaElement extends HTMLMetaElement{ append: ddeAppend<ddeHTMLMetaElement>; }
|
|
||||||
interface ddeHTMLMeterElement extends HTMLMeterElement{ append: ddeAppend<ddeHTMLMeterElement>; }
|
|
||||||
interface ddeHTMLObjectElement extends HTMLObjectElement{ append: ddeAppend<ddeHTMLObjectElement>; }
|
|
||||||
interface ddeHTMLOListElement extends HTMLOListElement{ append: ddeAppend<ddeHTMLOListElement>; }
|
|
||||||
interface ddeHTMLOptGroupElement extends HTMLOptGroupElement{ append: ddeAppend<ddeHTMLOptGroupElement>; }
|
|
||||||
interface ddeHTMLOptionElement extends HTMLOptionElement{ append: ddeAppend<ddeHTMLOptionElement>; }
|
|
||||||
interface ddeHTMLOutputElement extends HTMLOutputElement{ append: ddeAppend<ddeHTMLOutputElement>; }
|
|
||||||
interface ddeHTMLParagraphElement extends HTMLParagraphElement{ append: ddeAppend<ddeHTMLParagraphElement>; }
|
|
||||||
interface ddeHTMLPictureElement extends HTMLPictureElement{ append: ddeAppend<ddeHTMLPictureElement>; }
|
|
||||||
interface ddeHTMLPreElement extends HTMLPreElement{ append: ddeAppend<ddeHTMLPreElement>; }
|
|
||||||
interface ddeHTMLProgressElement extends HTMLProgressElement{ append: ddeAppend<ddeHTMLProgressElement>; }
|
|
||||||
interface ddeHTMLScriptElement extends HTMLScriptElement{ append: ddeAppend<ddeHTMLScriptElement>; }
|
|
||||||
interface ddeHTMLSelectElement extends HTMLSelectElement{ append: ddeAppend<ddeHTMLSelectElement>; }
|
|
||||||
interface ddeHTMLSlotElement extends HTMLSlotElement{ append: ddeAppend<ddeHTMLSlotElement>; }
|
|
||||||
interface ddeHTMLSourceElement extends HTMLSourceElement{ append: ddeAppend<ddeHTMLSourceElement>; }
|
|
||||||
interface ddeHTMLSpanElement extends HTMLSpanElement{ append: ddeAppend<ddeHTMLSpanElement>; }
|
|
||||||
interface ddeHTMLStyleElement extends HTMLStyleElement{ append: ddeAppend<ddeHTMLStyleElement>; }
|
|
||||||
interface ddeHTMLTableElement extends HTMLTableElement{ append: ddeAppend<ddeHTMLTableElement>; }
|
|
||||||
interface ddeHTMLTableSectionElement extends HTMLTableSectionElement{ append: ddeAppend<ddeHTMLTableSectionElement>; }
|
|
||||||
interface ddeHTMLTableCellElement extends HTMLTableCellElement{ append: ddeAppend<ddeHTMLTableCellElement>; }
|
|
||||||
interface ddeHTMLTemplateElement extends HTMLTemplateElement{ append: ddeAppend<ddeHTMLTemplateElement>; }
|
|
||||||
interface ddeHTMLTextAreaElement extends HTMLTextAreaElement{ append: ddeAppend<ddeHTMLTextAreaElement>; }
|
|
||||||
interface ddeHTMLTableCellElement extends HTMLTableCellElement{ append: ddeAppend<ddeHTMLTableCellElement>; }
|
|
||||||
interface ddeHTMLTimeElement extends HTMLTimeElement{ append: ddeAppend<ddeHTMLTimeElement>; }
|
|
||||||
interface ddeHTMLTitleElement extends HTMLTitleElement{ append: ddeAppend<ddeHTMLTitleElement>; }
|
|
||||||
interface ddeHTMLTableRowElement extends HTMLTableRowElement{ append: ddeAppend<ddeHTMLTableRowElement>; }
|
|
||||||
interface ddeHTMLTrackElement extends HTMLTrackElement{ append: ddeAppend<ddeHTMLTrackElement>; }
|
|
||||||
interface ddeHTMLUListElement extends HTMLUListElement{ append: ddeAppend<ddeHTMLUListElement>; }
|
|
||||||
interface ddeHTMLVideoElement extends HTMLVideoElement{ append: ddeAppend<ddeHTMLVideoElement>; }
|
|
||||||
interface ddeSVGAElement extends SVGAElement{ append: ddeAppend<ddeSVGAElement>; }
|
|
||||||
interface ddeSVGAnimateElement extends SVGAnimateElement{ append: ddeAppend<ddeSVGAnimateElement>; }
|
|
||||||
interface ddeSVGAnimateMotionElement extends SVGAnimateMotionElement{ append: ddeAppend<ddeSVGAnimateMotionElement>; }
|
|
||||||
interface ddeSVGAnimateTransformElement extends SVGAnimateTransformElement{ append: ddeAppend<ddeSVGAnimateTransformElement>; }
|
|
||||||
interface ddeSVGCircleElement extends SVGCircleElement{ append: ddeAppend<ddeSVGCircleElement>; }
|
|
||||||
interface ddeSVGClipPathElement extends SVGClipPathElement{ append: ddeAppend<ddeSVGClipPathElement>; }
|
|
||||||
interface ddeSVGDefsElement extends SVGDefsElement{ append: ddeAppend<ddeSVGDefsElement>; }
|
|
||||||
interface ddeSVGDescElement extends SVGDescElement{ append: ddeAppend<ddeSVGDescElement>; }
|
|
||||||
interface ddeSVGEllipseElement extends SVGEllipseElement{ append: ddeAppend<ddeSVGEllipseElement>; }
|
|
||||||
interface ddeSVGFEBlendElement extends SVGFEBlendElement{ append: ddeAppend<ddeSVGFEBlendElement>; }
|
|
||||||
interface ddeSVGFEColorMatrixElement extends SVGFEColorMatrixElement{ append: ddeAppend<ddeSVGFEColorMatrixElement>; }
|
|
||||||
interface ddeSVGFEComponentTransferElement extends SVGFEComponentTransferElement{ append: ddeAppend<ddeSVGFEComponentTransferElement>; }
|
|
||||||
interface ddeSVGFECompositeElement extends SVGFECompositeElement{ append: ddeAppend<ddeSVGFECompositeElement>; }
|
|
||||||
interface ddeSVGFEConvolveMatrixElement extends SVGFEConvolveMatrixElement{ append: ddeAppend<ddeSVGFEConvolveMatrixElement>; }
|
|
||||||
interface ddeSVGFEDiffuseLightingElement extends SVGFEDiffuseLightingElement{ append: ddeAppend<ddeSVGFEDiffuseLightingElement>; }
|
|
||||||
interface ddeSVGFEDisplacementMapElement extends SVGFEDisplacementMapElement{ append: ddeAppend<ddeSVGFEDisplacementMapElement>; }
|
|
||||||
interface ddeSVGFEDistantLightElement extends SVGFEDistantLightElement{ append: ddeAppend<ddeSVGFEDistantLightElement>; }
|
|
||||||
interface ddeSVGFEDropShadowElement extends SVGFEDropShadowElement{ append: ddeAppend<ddeSVGFEDropShadowElement>; }
|
|
||||||
interface ddeSVGFEFloodElement extends SVGFEFloodElement{ append: ddeAppend<ddeSVGFEFloodElement>; }
|
|
||||||
interface ddeSVGFEFuncAElement extends SVGFEFuncAElement{ append: ddeAppend<ddeSVGFEFuncAElement>; }
|
|
||||||
interface ddeSVGFEFuncBElement extends SVGFEFuncBElement{ append: ddeAppend<ddeSVGFEFuncBElement>; }
|
|
||||||
interface ddeSVGFEFuncGElement extends SVGFEFuncGElement{ append: ddeAppend<ddeSVGFEFuncGElement>; }
|
|
||||||
interface ddeSVGFEFuncRElement extends SVGFEFuncRElement{ append: ddeAppend<ddeSVGFEFuncRElement>; }
|
|
||||||
interface ddeSVGFEGaussianBlurElement extends SVGFEGaussianBlurElement{ append: ddeAppend<ddeSVGFEGaussianBlurElement>; }
|
|
||||||
interface ddeSVGFEImageElement extends SVGFEImageElement{ append: ddeAppend<ddeSVGFEImageElement>; }
|
|
||||||
interface ddeSVGFEMergeElement extends SVGFEMergeElement{ append: ddeAppend<ddeSVGFEMergeElement>; }
|
|
||||||
interface ddeSVGFEMergeNodeElement extends SVGFEMergeNodeElement{ append: ddeAppend<ddeSVGFEMergeNodeElement>; }
|
|
||||||
interface ddeSVGFEMorphologyElement extends SVGFEMorphologyElement{ append: ddeAppend<ddeSVGFEMorphologyElement>; }
|
|
||||||
interface ddeSVGFEOffsetElement extends SVGFEOffsetElement{ append: ddeAppend<ddeSVGFEOffsetElement>; }
|
|
||||||
interface ddeSVGFEPointLightElement extends SVGFEPointLightElement{ append: ddeAppend<ddeSVGFEPointLightElement>; }
|
|
||||||
interface ddeSVGFESpecularLightingElement extends SVGFESpecularLightingElement{ append: ddeAppend<ddeSVGFESpecularLightingElement>; }
|
|
||||||
interface ddeSVGFESpotLightElement extends SVGFESpotLightElement{ append: ddeAppend<ddeSVGFESpotLightElement>; }
|
|
||||||
interface ddeSVGFETileElement extends SVGFETileElement{ append: ddeAppend<ddeSVGFETileElement>; }
|
|
||||||
interface ddeSVGFETurbulenceElement extends SVGFETurbulenceElement{ append: ddeAppend<ddeSVGFETurbulenceElement>; }
|
|
||||||
interface ddeSVGFilterElement extends SVGFilterElement{ append: ddeAppend<ddeSVGFilterElement>; }
|
|
||||||
interface ddeSVGForeignObjectElement extends SVGForeignObjectElement{ append: ddeAppend<ddeSVGForeignObjectElement>; }
|
|
||||||
interface ddeSVGGElement extends SVGGElement{ append: ddeAppend<ddeSVGGElement>; }
|
|
||||||
interface ddeSVGImageElement extends SVGImageElement{ append: ddeAppend<ddeSVGImageElement>; }
|
|
||||||
interface ddeSVGLineElement extends SVGLineElement{ append: ddeAppend<ddeSVGLineElement>; }
|
|
||||||
interface ddeSVGLinearGradientElement extends SVGLinearGradientElement{ append: ddeAppend<ddeSVGLinearGradientElement>; }
|
|
||||||
interface ddeSVGMarkerElement extends SVGMarkerElement{ append: ddeAppend<ddeSVGMarkerElement>; }
|
|
||||||
interface ddeSVGMaskElement extends SVGMaskElement{ append: ddeAppend<ddeSVGMaskElement>; }
|
|
||||||
interface ddeSVGMetadataElement extends SVGMetadataElement{ append: ddeAppend<ddeSVGMetadataElement>; }
|
|
||||||
interface ddeSVGMPathElement extends SVGMPathElement{ append: ddeAppend<ddeSVGMPathElement>; }
|
|
||||||
interface ddeSVGPathElement extends SVGPathElement{ append: ddeAppend<ddeSVGPathElement>; }
|
|
||||||
interface ddeSVGPatternElement extends SVGPatternElement{ append: ddeAppend<ddeSVGPatternElement>; }
|
|
||||||
interface ddeSVGPolygonElement extends SVGPolygonElement{ append: ddeAppend<ddeSVGPolygonElement>; }
|
|
||||||
interface ddeSVGPolylineElement extends SVGPolylineElement{ append: ddeAppend<ddeSVGPolylineElement>; }
|
|
||||||
interface ddeSVGRadialGradientElement extends SVGRadialGradientElement{ append: ddeAppend<ddeSVGRadialGradientElement>; }
|
|
||||||
interface ddeSVGRectElement extends SVGRectElement{ append: ddeAppend<ddeSVGRectElement>; }
|
|
||||||
interface ddeSVGScriptElement extends SVGScriptElement{ append: ddeAppend<ddeSVGScriptElement>; }
|
|
||||||
interface ddeSVGSetElement extends SVGSetElement{ append: ddeAppend<ddeSVGSetElement>; }
|
|
||||||
interface ddeSVGStopElement extends SVGStopElement{ append: ddeAppend<ddeSVGStopElement>; }
|
|
||||||
interface ddeSVGStyleElement extends SVGStyleElement{ append: ddeAppend<ddeSVGStyleElement>; }
|
|
||||||
interface ddeSVGSVGElement extends SVGSVGElement{ append: ddeAppend<ddeSVGSVGElement>; }
|
|
||||||
interface ddeSVGSwitchElement extends SVGSwitchElement{ append: ddeAppend<ddeSVGSwitchElement>; }
|
|
||||||
interface ddeSVGSymbolElement extends SVGSymbolElement{ append: ddeAppend<ddeSVGSymbolElement>; }
|
|
||||||
interface ddeSVGTextElement extends SVGTextElement{ append: ddeAppend<ddeSVGTextElement>; }
|
|
||||||
interface ddeSVGTextPathElement extends SVGTextPathElement{ append: ddeAppend<ddeSVGTextPathElement>; }
|
|
||||||
interface ddeSVGTitleElement extends SVGTitleElement{ append: ddeAppend<ddeSVGTitleElement>; }
|
|
||||||
interface ddeSVGTSpanElement extends SVGTSpanElement{ append: ddeAppend<ddeSVGTSpanElement>; }
|
|
||||||
interface ddeSVGUseElement extends SVGUseElement{ append: ddeAppend<ddeSVGUseElement>; }
|
|
||||||
interface ddeSVGViewElement extends SVGViewElement{ append: ddeAppend<ddeSVGViewElement>; }
|
|
||||||
// editorconfig-checker-enable
|
|
||||||
export interface Signal<V, A> {
|
|
||||||
/** The current value of the signal */
|
|
||||||
get(): V;
|
|
||||||
/** Set new value of the signal */
|
|
||||||
set(value: V): V;
|
|
||||||
toJSON(): V;
|
|
||||||
valueOf(): V;
|
|
||||||
}
|
|
||||||
type Action<V>= (this: { value: V, stopPropagation(): void }, ...a: any[])=> typeof signal._ | void;
|
|
||||||
//type SymbolSignal= Symbol;
|
|
||||||
type SymbolOnclear= symbol;
|
|
||||||
type Actions<V>= Record<string | SymbolOnclear, Action<V>>;
|
|
||||||
type OnListenerOptions= Pick<AddEventListenerOptions, "signal"> & { first_time?: boolean };
|
|
||||||
interface signal{
|
|
||||||
_: Symbol
|
|
||||||
/**
|
|
||||||
* Computations signal. This creates a signal which is computed from other signals.
|
|
||||||
* */
|
|
||||||
<V extends ()=> any>(computation: V): Signal<ReturnType<V>, {}>
|
|
||||||
/**
|
|
||||||
* Simple example:
|
|
||||||
* ```js
|
|
||||||
* const hello= S("Hello Signal");
|
|
||||||
* ```
|
|
||||||
* …simple todo signal:
|
|
||||||
* ```js
|
|
||||||
* const todos= S([], {
|
|
||||||
* add(v){ this.value.push(S(v)); },
|
|
||||||
* remove(i){ this.value.splice(i, 1); },
|
|
||||||
* [S.symbols.onclear](){ S.clear(...this.value); },
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
* …computed signal:
|
|
||||||
* ```js
|
|
||||||
* const name= S("Jan");
|
|
||||||
* const surname= S("Andrle");
|
|
||||||
* const fullname= S(()=> name.get()+" "+surname.get());
|
|
||||||
* ```
|
|
||||||
* @param value Initial signal value. Or function computing value from other signals.
|
|
||||||
* @param actions Use to define actions on the signal. Such as add item to the array.
|
|
||||||
* There is also a reserved function `S.symbol.onclear` which is called when the signal is cleared
|
|
||||||
* by `S.clear`.
|
|
||||||
* */
|
|
||||||
<V, A extends Actions<V>>(value: V, actions?: A): Signal<V, A>;
|
|
||||||
action<S extends Signal<any, Actions<any>>, A extends (S extends Signal<any, infer A> ? A : never), N extends keyof A>(
|
|
||||||
signal: S,
|
|
||||||
name: N,
|
|
||||||
...params: A[N] extends (...args: infer P)=> any ? P : never
|
|
||||||
): void;
|
|
||||||
clear(...signals: Signal<any, any>[]): void;
|
|
||||||
on<T>(signal: Signal<T, any>, onchange: (a: T)=> void, options?: OnListenerOptions): void;
|
|
||||||
symbols: {
|
|
||||||
//signal: SymbolSignal;
|
|
||||||
onclear: SymbolOnclear;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Reactive element, which is rendered based on the given signal.
|
|
||||||
* ```js
|
|
||||||
* S.el(signal, value=> value ? el("b", "True") : el("i", "False"));
|
|
||||||
* S.el(listS, list=> list.map(li=> el("li", li)));
|
|
||||||
* ```
|
|
||||||
* */
|
|
||||||
el<S extends any>(signal: Signal<S, any>, el: (v: S)=> Element | Element[] | DocumentFragment): DocumentFragment;
|
|
||||||
|
|
||||||
observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>;
|
|
||||||
}
|
|
||||||
export const signal: signal;
|
|
||||||
export const S: signal;
|
|
||||||
declare global {
|
|
||||||
type ddeSignal<T, A= {}>= Signal<T, A>;
|
|
||||||
type ddeAction<V>= Action<V>
|
|
||||||
type ddeActions<V>= Actions<V>
|
|
||||||
}
|
|
1033
dist/esm-with-signals.d.ts
vendored
1033
dist/esm-with-signals.d.ts
vendored
File diff suppressed because it is too large
Load Diff
874
dist/esm-with-signals.js
vendored
874
dist/esm-with-signals.js
vendored
File diff suppressed because it is too large
Load Diff
860
dist/esm-with-signals.min.d.ts
vendored
Normal file
860
dist/esm-with-signals.min.d.ts
vendored
Normal file
@ -0,0 +1,860 @@
|
|||||||
|
// Generated by dts-bundle-generator v9.5.1
|
||||||
|
|
||||||
|
export interface Signal<V, A> {
|
||||||
|
/** The current value of the signal */
|
||||||
|
get(): V;
|
||||||
|
/** Set new value of the signal */
|
||||||
|
set(value: V, force?: boolean): V;
|
||||||
|
toJSON(): V;
|
||||||
|
valueOf(): V;
|
||||||
|
}
|
||||||
|
export type Action<V> = (this: {
|
||||||
|
value: V;
|
||||||
|
stopPropagation(): void;
|
||||||
|
}, ...a: any[]) => typeof signal._ | void;
|
||||||
|
//type SymbolSignal= Symbol;
|
||||||
|
export type SymbolOnclear = symbol;
|
||||||
|
export type Actions<V> = Record<string | SymbolOnclear, Action<V>>;
|
||||||
|
export type OnListenerOptions = Pick<AddEventListenerOptions, "signal"> & {
|
||||||
|
first_time?: boolean;
|
||||||
|
};
|
||||||
|
export type SElement = Node | Element | DocumentFragment | ddeHTMLElement | ddeSVGElement | ddeDocumentFragment;
|
||||||
|
export interface signal {
|
||||||
|
_: Symbol;
|
||||||
|
/**
|
||||||
|
* Computations signal. This creates a signal which is computed from other signals.
|
||||||
|
* */
|
||||||
|
<V extends () => any>(computation: V): Signal<ReturnType<V>, {}>;
|
||||||
|
/**
|
||||||
|
* Simple example:
|
||||||
|
* ```js
|
||||||
|
* const hello= S("Hello Signal");
|
||||||
|
* ```
|
||||||
|
* …simple todo signal:
|
||||||
|
* ```js
|
||||||
|
* const todos= S([], {
|
||||||
|
* add(v){ this.value.push(S(v)); },
|
||||||
|
* remove(i){ this.value.splice(i, 1); },
|
||||||
|
* [S.symbols.onclear](){ S.clear(...this.value); },
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* …computed signal:
|
||||||
|
* ```js
|
||||||
|
* const name= S("Jan");
|
||||||
|
* const surname= S("Andrle");
|
||||||
|
* const fullname= S(()=> name.get()+" "+surname.get());
|
||||||
|
* ```
|
||||||
|
* @param value Initial signal value. Or function computing value from other signals.
|
||||||
|
* @param actions Use to define actions on the signal. Such as add item to the array.
|
||||||
|
* There is also a reserved function `S.symbol.onclear` which is called when the signal is cleared
|
||||||
|
* by `S.clear`.
|
||||||
|
* */
|
||||||
|
<V, A extends Actions<V>>(value: V, actions?: A): Signal<V, A>;
|
||||||
|
action<S extends Signal<any, Actions<any>>, A extends (S extends Signal<any, infer A> ? A : never), N extends keyof A>(signal: S, name: N, ...params: A[N] extends (...args: infer P) => any ? P : never): void;
|
||||||
|
clear(...signals: Signal<any, any>[]): void;
|
||||||
|
on<T>(signal: Signal<T, any>, onchange: (a: T) => void, options?: OnListenerOptions): void;
|
||||||
|
symbols: {
|
||||||
|
//signal: SymbolSignal;
|
||||||
|
onclear: SymbolOnclear;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Reactive element, which is rendered based on the given signal.
|
||||||
|
* ```js
|
||||||
|
* S.el(signal, value=> value ? el("b", "True") : el("i", "False"));
|
||||||
|
* S.el(listS, list=> list.map(li=> el("li", li)));
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
el<S extends any>(signal: Signal<S, any>, el: (v: S) => SElement | SElement[]): DocumentFragment;
|
||||||
|
observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>;
|
||||||
|
}
|
||||||
|
export const signal: signal;
|
||||||
|
export const S: signal;
|
||||||
|
declare global {
|
||||||
|
type ddeSignal<T, A = {}> = Signal<T, A>;
|
||||||
|
type ddeAction<V> = Action<V>;
|
||||||
|
type ddeActions<V> = Actions<V>;
|
||||||
|
}
|
||||||
|
export type CustomElementTagNameMap = {
|
||||||
|
"#text": Text;
|
||||||
|
"#comment": Comment;
|
||||||
|
};
|
||||||
|
export type SupportedElement = HTMLElementTagNameMap[keyof HTMLElementTagNameMap] | SVGElementTagNameMap[keyof SVGElementTagNameMap] | MathMLElementTagNameMap[keyof MathMLElementTagNameMap] | CustomElementTagNameMap[keyof CustomElementTagNameMap];
|
||||||
|
declare global {
|
||||||
|
type ddeComponentAttributes = Record<any, any> | undefined;
|
||||||
|
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El, ...rest: any) => any;
|
||||||
|
type ddeString = string | Signal<string, {}>;
|
||||||
|
type ddeStringable = ddeString | number | Signal<number, {}>;
|
||||||
|
}
|
||||||
|
export type Host<EL extends SupportedElement> = (...addons: ddeElementAddon<EL>[]) => EL;
|
||||||
|
export type PascalCase = `${Capitalize<string>}${string}`;
|
||||||
|
export type AttrsModified = {
|
||||||
|
/**
|
||||||
|
* Use string like in HTML (internally uses `*.setAttribute("style", *)`), or object representation (like DOM API).
|
||||||
|
*/
|
||||||
|
style: Partial<CSSStyleDeclaration> | ddeString | Partial<{
|
||||||
|
[K in keyof CSSStyleDeclaration]: Signal<CSSStyleDeclaration[K], {}>;
|
||||||
|
}>;
|
||||||
|
/**
|
||||||
|
* Provide option to add/remove/toggle CSS clasess (index of object) using 1/0/-1.
|
||||||
|
* In fact `el.classList.toggle(class_name)` for `-1` and `el.classList.toggle(class_name, Boolean(...))`
|
||||||
|
* for others.
|
||||||
|
*/
|
||||||
|
classList: Record<string, -1 | 0 | 1 | boolean | Signal<-1 | 0 | 1 | boolean, {}>>;
|
||||||
|
/**
|
||||||
|
* Used by the dataset HTML attribute to represent data for custom attributes added to elements.
|
||||||
|
* Values are converted to string (see {@link DOMStringMap}).
|
||||||
|
*
|
||||||
|
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMStringMap)
|
||||||
|
* */
|
||||||
|
dataset: Record<string, ddeStringable>;
|
||||||
|
/**
|
||||||
|
* Sets `aria-*` simiraly to `dataset`
|
||||||
|
* */
|
||||||
|
ariaset: Record<string, ddeString>;
|
||||||
|
} & Record<`=${string}` | `data${PascalCase}` | `aria${PascalCase}`, ddeString> & Record<`.${string}`, any>;
|
||||||
|
export type _fromElsInterfaces<EL extends SupportedElement> = Omit<EL, keyof AttrsModified>;
|
||||||
|
export type IsReadonly<T, K extends keyof T> = T extends {
|
||||||
|
readonly [P in K]: T[K];
|
||||||
|
} ? true : false;
|
||||||
|
/**
|
||||||
|
* Just element attributtes
|
||||||
|
*
|
||||||
|
* In most cases, you can use native propertie such as
|
||||||
|
* [MDN WEB/API/Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) and so on
|
||||||
|
* (e.g. [`Text`](https://developer.mozilla.org/en-US/docs/Web/API/Text)).
|
||||||
|
*
|
||||||
|
* There is added support for `data[A-Z].*`/`aria[A-Z].*` to be converted to the kebab-case alternatives.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export type ElementAttributes<T extends SupportedElement> = Partial<{
|
||||||
|
[K in keyof _fromElsInterfaces<T>]: _fromElsInterfaces<T>[K] extends ((...p: any[]) => any) ? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>) => Signal<ReturnType<_fromElsInterfaces<T>[K]>, {}>) : (IsReadonly<_fromElsInterfaces<T>, K> extends false ? _fromElsInterfaces<T>[K] | Signal<_fromElsInterfaces<T>[K], {}> : ddeStringable);
|
||||||
|
} & AttrsModified> & Record<string, any>;
|
||||||
|
export function classListDeclarative<El extends SupportedElement>(element: El, classList: AttrsModified["classList"]): El;
|
||||||
|
export function assign<El extends SupportedElement>(element: El, ...attrs_array: ElementAttributes<El>[]): El;
|
||||||
|
export function assignAttribute<El extends SupportedElement, ATT extends keyof ElementAttributes<El>>(element: El, attr: ATT, value: ElementAttributes<El>[ATT]): ElementAttributes<El>[ATT];
|
||||||
|
export type ExtendedHTMLElementTagNameMap = HTMLElementTagNameMap & CustomElementTagNameMap;
|
||||||
|
export namespace el {
|
||||||
|
/**
|
||||||
|
* Creates a marker comment for elements
|
||||||
|
*
|
||||||
|
* @param attrs - Marker attributes
|
||||||
|
* @param [is_open=false] - Whether the marker is open-ended
|
||||||
|
* @returns Comment node marker
|
||||||
|
*/
|
||||||
|
export function mark(attrs: {
|
||||||
|
type: "component" | "reactive" | "later";
|
||||||
|
name?: string;
|
||||||
|
host?: "this" | "parentElement";
|
||||||
|
}, is_open?: boolean): Comment;
|
||||||
|
}
|
||||||
|
export function chainableAppend<EL extends SupportedElement>(el: EL): EL | ddeHTMLElement;
|
||||||
|
export function el<A extends ddeComponentAttributes, EL extends SupportedElement | ddeDocumentFragment>(component: (attr: A, ...rest: any[]) => EL, attrs?: NoInfer<A>, ...addons: ddeElementAddon<EL>[]): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? EL : (EL extends ddeDocumentFragment ? EL : ddeHTMLElement);
|
||||||
|
export function el<A extends {
|
||||||
|
textContent: ddeStringable;
|
||||||
|
}, EL extends SupportedElement | ddeDocumentFragment>(component: (attr: A, ...rest: any[]) => EL, attrs?: NoInfer<A>["textContent"], ...addons: ddeElementAddon<EL>[]): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? EL : (EL extends ddeDocumentFragment ? EL : ddeHTMLElement);
|
||||||
|
export function el<TAG extends keyof ExtendedHTMLElementTagNameMap>(tag_name: TAG, attrs?: ElementAttributes<ExtendedHTMLElementTagNameMap[NoInfer<TAG>]> | ddeStringable, ...addons: ddeElementAddon<ExtendedHTMLElementTagNameMap[NoInfer<TAG>]>[]): TAG extends keyof ddeHTMLElementTagNameMap ? ddeHTMLElementTagNameMap[TAG] : ddeHTMLElement;
|
||||||
|
export function el(tag_name?: "<>"): ddeDocumentFragment;
|
||||||
|
export function el(tag_name: string, attrs?: ElementAttributes<HTMLElement> | ddeStringable, ...addons: ddeElementAddon<HTMLElement>[]): ddeHTMLElement;
|
||||||
|
export function elNS(namespace: "http://www.w3.org/2000/svg"): <TAG extends keyof SVGElementTagNameMap & string, EL extends (TAG extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[TAG] : SVGElement)>(tag_name: TAG, attrs?: ElementAttributes<NoInfer<EL>> | ddeStringable, ...addons: ddeElementAddon<NoInfer<EL>>[]) => TAG extends keyof ddeSVGElementTagNameMap ? ddeSVGElementTagNameMap[TAG] : ddeSVGElement;
|
||||||
|
export function elNS(namespace: "http://www.w3.org/1998/Math/MathML"): <TAG extends keyof MathMLElementTagNameMap & string, EL extends (TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement)>(tag_name: TAG, attrs?: ddeStringable | Partial<{
|
||||||
|
[key in keyof EL]: EL[key] | Signal<EL[key], {}> | string | number | boolean;
|
||||||
|
}>, ...addons: ddeElementAddon<NoInfer<EL>>[]) => ddeMathMLElement;
|
||||||
|
export function elNS(namespace: string): (tag_name: string, attrs?: string | ddeStringable | Record<string, any>, ...addons: ddeElementAddon<SupportedElement>[]) => SupportedElement;
|
||||||
|
/** Simulate slots for ddeComponents */
|
||||||
|
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(root: EL): EL;
|
||||||
|
/**
|
||||||
|
* Simulate slots in Custom Elements without using `shadowRoot`.
|
||||||
|
* @param el Custom Element root element
|
||||||
|
* @param body Body of the custom element
|
||||||
|
* */
|
||||||
|
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(el: HTMLElement, body: EL): EL;
|
||||||
|
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, host: Host<SupportedElement>): (data?: any) => void;
|
||||||
|
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?: EventInit): (element: SupportedElement, data?: any) => void;
|
||||||
|
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
|
||||||
|
export interface On {
|
||||||
|
/** Listens to the DOM event. See {@link Document.addEventListener} */
|
||||||
|
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & {
|
||||||
|
target: EL;
|
||||||
|
}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
|
||||||
|
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
|
||||||
|
/** 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>;
|
||||||
|
/** 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>;
|
||||||
|
/**
|
||||||
|
* Fires after the next tick of the Javascript event loop.
|
||||||
|
* This is handy for example to apply some property depending on the element content:
|
||||||
|
* ```js
|
||||||
|
* const selected= "Z";
|
||||||
|
* //...
|
||||||
|
* return el("form").append(
|
||||||
|
* el("select", null, on.defer(e=> e.value=selected)).append(
|
||||||
|
* el("option", { value: "A", textContent: "A" }),
|
||||||
|
* //...
|
||||||
|
* el("option", { value: "Z", textContent: "Z" }),
|
||||||
|
* ),
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>;
|
||||||
|
}
|
||||||
|
export const on: On;
|
||||||
|
export type Scope = {
|
||||||
|
scope: Node | Function | Object;
|
||||||
|
host: Host<SupportedElement>;
|
||||||
|
custom_element: false | HTMLElement;
|
||||||
|
prevent: boolean;
|
||||||
|
};
|
||||||
|
/** Current scope created last time the `el(Function)` was invoke. (Or {@link scope.push}) */
|
||||||
|
export const scope: {
|
||||||
|
current: Scope;
|
||||||
|
/** Stops all automatizations. E. g. signals used as attributes in current scope
|
||||||
|
* registers removing these listeners (and clean signal if no other listeners are detected)
|
||||||
|
* on `disconnected` event. */
|
||||||
|
preventDefault<T extends boolean>(prevent: T): T;
|
||||||
|
/**
|
||||||
|
* This represents reference to the current host element — `scope.host()`.
|
||||||
|
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
||||||
|
* — `scope.host(on.connected(console.log))`.
|
||||||
|
* */
|
||||||
|
host: Host<SupportedElement>;
|
||||||
|
/**
|
||||||
|
* Creates/gets an AbortController that triggers when the element disconnects
|
||||||
|
* */
|
||||||
|
signal: AbortSignal;
|
||||||
|
state: Scope[];
|
||||||
|
/** Adds new child scope. All attributes are inherited by default. */
|
||||||
|
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>;
|
||||||
|
/** Adds root scope as a child of the current scope. */
|
||||||
|
pushRoot(): ReturnType<Array<Scope>["push"]>;
|
||||||
|
/** Removes last/current child scope. */
|
||||||
|
pop(): ReturnType<Array<Scope>["pop"]>;
|
||||||
|
};
|
||||||
|
export function customElementRender<EL extends HTMLElement, P extends any = Record<string, string | Signal<string, {}>>>(target: ShadowRoot | EL, render: (props: P) => SupportedElement | DocumentFragment, props?: P | ((el: EL) => P)): EL;
|
||||||
|
export function customElementWithDDE<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||||
|
export function lifecyclesToEvents<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||||
|
/**
|
||||||
|
* This is used primarly for server side rendering. To be sure that all async operations
|
||||||
|
* are finished before the page is sent to the client.
|
||||||
|
* ```
|
||||||
|
* // on component
|
||||||
|
* function component(){
|
||||||
|
* …
|
||||||
|
* queue(fetch(...).then(...));
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // building the page
|
||||||
|
* async function build(){
|
||||||
|
* const { component }= await import("./component.js");
|
||||||
|
* document.body.append(el(component));
|
||||||
|
* await queue();
|
||||||
|
* retutn document.body.innerHTML;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
export function queue(promise?: Promise<unknown>): Promise<unknown>;
|
||||||
|
/**
|
||||||
|
* Memoization utility for caching DOM elements to improve performance.
|
||||||
|
* Used to prevent unnecessary recreation of elements when rendering lists or complex components.
|
||||||
|
*
|
||||||
|
* @param key - Unique identifier for the element (usually an ID or unique value)
|
||||||
|
* @param generator - Function that creates the element
|
||||||
|
* @returns The cached element if the key exists, otherwise the result of the generator function
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* // Within S.el for list rendering
|
||||||
|
* S.el(itemsSignal, (items, memo) =>
|
||||||
|
* el("ul").append(
|
||||||
|
* ...items.map(item =>
|
||||||
|
* memo(item.id, () => el(ItemComponent, item))
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function memo<T>(key: string | number | object, generator: (key: any) => T): T;
|
||||||
|
/**
|
||||||
|
* Memo namespace containing utility functions for memoization.
|
||||||
|
*/
|
||||||
|
export namespace memo {
|
||||||
|
/**
|
||||||
|
* Checks if an object is a memo scope.
|
||||||
|
* @param obj - The object to check
|
||||||
|
* @returns True if the object is a memo scope
|
||||||
|
*/
|
||||||
|
export function isScope(obj: any): boolean;
|
||||||
|
/**
|
||||||
|
* Creates a memoized function with optional cleanup support.
|
||||||
|
*
|
||||||
|
* @param fun - The function to memoize
|
||||||
|
* @param options - Configuration options
|
||||||
|
* @param options.signal - AbortSignal for cleanup
|
||||||
|
* @param options.onlyLast - When true, only keeps the cache from the most recent call
|
||||||
|
* @returns A memoized version of the function with a .clear() method
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const renderItems = memo.scope(function(items) {
|
||||||
|
* return items.map(item =>
|
||||||
|
* memo(item.id, () => el("div", item.name))
|
||||||
|
* );
|
||||||
|
* }, {
|
||||||
|
* signal: controller.signal,
|
||||||
|
* onlyLast: true
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function scope<F extends Function>(fun: F, options?: {
|
||||||
|
signal?: AbortSignal;
|
||||||
|
onlyLast?: boolean;
|
||||||
|
}): F & {
|
||||||
|
clear: () => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/* TypeScript MEH */
|
||||||
|
declare global {
|
||||||
|
type ddeAppend<el> = (...nodes: (Node | string)[]) => el;
|
||||||
|
interface ddeDocumentFragment extends DocumentFragment {
|
||||||
|
append: ddeAppend<ddeDocumentFragment>;
|
||||||
|
}
|
||||||
|
interface ddeHTMLElement extends HTMLElement {
|
||||||
|
append: ddeAppend<ddeHTMLElement>;
|
||||||
|
}
|
||||||
|
interface ddeSVGElement extends SVGElement {
|
||||||
|
append: ddeAppend<ddeSVGElement>;
|
||||||
|
}
|
||||||
|
interface ddeMathMLElement extends MathMLElement {
|
||||||
|
append: ddeAppend<ddeMathMLElement>;
|
||||||
|
}
|
||||||
|
interface ddeHTMLElementTagNameMap {
|
||||||
|
"a": ddeHTMLAnchorElement;
|
||||||
|
"area": ddeHTMLAreaElement;
|
||||||
|
"audio": ddeHTMLAudioElement;
|
||||||
|
"base": ddeHTMLBaseElement;
|
||||||
|
"blockquote": ddeHTMLQuoteElement;
|
||||||
|
"body": ddeHTMLBodyElement;
|
||||||
|
"br": ddeHTMLBRElement;
|
||||||
|
"button": ddeHTMLButtonElement;
|
||||||
|
"canvas": ddeHTMLCanvasElement;
|
||||||
|
"caption": ddeHTMLTableCaptionElement;
|
||||||
|
"col": ddeHTMLTableColElement;
|
||||||
|
"colgroup": ddeHTMLTableColElement;
|
||||||
|
"data": ddeHTMLDataElement;
|
||||||
|
"datalist": ddeHTMLDataListElement;
|
||||||
|
"del": ddeHTMLModElement;
|
||||||
|
"details": ddeHTMLDetailsElement;
|
||||||
|
"dialog": ddeHTMLDialogElement;
|
||||||
|
"div": ddeHTMLDivElement;
|
||||||
|
"dl": ddeHTMLDListElement;
|
||||||
|
"embed": ddeHTMLEmbedElement;
|
||||||
|
"fieldset": ddeHTMLFieldSetElement;
|
||||||
|
"form": ddeHTMLFormElement;
|
||||||
|
"h1": ddeHTMLHeadingElement;
|
||||||
|
"h2": ddeHTMLHeadingElement;
|
||||||
|
"h3": ddeHTMLHeadingElement;
|
||||||
|
"h4": ddeHTMLHeadingElement;
|
||||||
|
"h5": ddeHTMLHeadingElement;
|
||||||
|
"h6": ddeHTMLHeadingElement;
|
||||||
|
"head": ddeHTMLHeadElement;
|
||||||
|
"hr": ddeHTMLHRElement;
|
||||||
|
"html": ddeHTMLHtmlElement;
|
||||||
|
"iframe": ddeHTMLIFrameElement;
|
||||||
|
"img": ddeHTMLImageElement;
|
||||||
|
"input": ddeHTMLInputElement;
|
||||||
|
"ins": ddeHTMLModElement;
|
||||||
|
"label": ddeHTMLLabelElement;
|
||||||
|
"legend": ddeHTMLLegendElement;
|
||||||
|
"li": ddeHTMLLIElement;
|
||||||
|
"link": ddeHTMLLinkElement;
|
||||||
|
"map": ddeHTMLMapElement;
|
||||||
|
"menu": ddeHTMLMenuElement;
|
||||||
|
"meta": ddeHTMLMetaElement;
|
||||||
|
"meter": ddeHTMLMeterElement;
|
||||||
|
"object": ddeHTMLObjectElement;
|
||||||
|
"ol": ddeHTMLOListElement;
|
||||||
|
"optgroup": ddeHTMLOptGroupElement;
|
||||||
|
"option": ddeHTMLOptionElement;
|
||||||
|
"output": ddeHTMLOutputElement;
|
||||||
|
"p": ddeHTMLParagraphElement;
|
||||||
|
"picture": ddeHTMLPictureElement;
|
||||||
|
"pre": ddeHTMLPreElement;
|
||||||
|
"progress": ddeHTMLProgressElement;
|
||||||
|
"q": ddeHTMLQuoteElement;
|
||||||
|
"script": ddeHTMLScriptElement;
|
||||||
|
"select": ddeHTMLSelectElement;
|
||||||
|
"slot": ddeHTMLSlotElement;
|
||||||
|
"source": ddeHTMLSourceElement;
|
||||||
|
"span": ddeHTMLSpanElement;
|
||||||
|
"style": ddeHTMLStyleElement;
|
||||||
|
"table": ddeHTMLTableElement;
|
||||||
|
"tbody": ddeHTMLTableSectionElement;
|
||||||
|
"td": ddeHTMLTableCellElement;
|
||||||
|
"template": ddeHTMLTemplateElement;
|
||||||
|
"textarea": ddeHTMLTextAreaElement;
|
||||||
|
"tfoot": ddeHTMLTableSectionElement;
|
||||||
|
"th": ddeHTMLTableCellElement;
|
||||||
|
"thead": ddeHTMLTableSectionElement;
|
||||||
|
"time": ddeHTMLTimeElement;
|
||||||
|
"title": ddeHTMLTitleElement;
|
||||||
|
"tr": ddeHTMLTableRowElement;
|
||||||
|
"track": ddeHTMLTrackElement;
|
||||||
|
"ul": ddeHTMLUListElement;
|
||||||
|
"video": ddeHTMLVideoElement;
|
||||||
|
}
|
||||||
|
interface ddeSVGElementTagNameMap {
|
||||||
|
"a": ddeSVGAElement;
|
||||||
|
"animate": ddeSVGAnimateElement;
|
||||||
|
"animateMotion": ddeSVGAnimateMotionElement;
|
||||||
|
"animateTransform": ddeSVGAnimateTransformElement;
|
||||||
|
"circle": ddeSVGCircleElement;
|
||||||
|
"clipPath": ddeSVGClipPathElement;
|
||||||
|
"defs": ddeSVGDefsElement;
|
||||||
|
"desc": ddeSVGDescElement;
|
||||||
|
"ellipse": ddeSVGEllipseElement;
|
||||||
|
"feBlend": ddeSVGFEBlendElement;
|
||||||
|
"feColorMatrix": ddeSVGFEColorMatrixElement;
|
||||||
|
"feComponentTransfer": ddeSVGFEComponentTransferElement;
|
||||||
|
"feComposite": ddeSVGFECompositeElement;
|
||||||
|
"feConvolveMatrix": ddeSVGFEConvolveMatrixElement;
|
||||||
|
"feDiffuseLighting": ddeSVGFEDiffuseLightingElement;
|
||||||
|
"feDisplacementMap": ddeSVGFEDisplacementMapElement;
|
||||||
|
"feDistantLight": ddeSVGFEDistantLightElement;
|
||||||
|
"feDropShadow": ddeSVGFEDropShadowElement;
|
||||||
|
"feFlood": ddeSVGFEFloodElement;
|
||||||
|
"feFuncA": ddeSVGFEFuncAElement;
|
||||||
|
"feFuncB": ddeSVGFEFuncBElement;
|
||||||
|
"feFuncG": ddeSVGFEFuncGElement;
|
||||||
|
"feFuncR": ddeSVGFEFuncRElement;
|
||||||
|
"feGaussianBlur": ddeSVGFEGaussianBlurElement;
|
||||||
|
"feImage": ddeSVGFEImageElement;
|
||||||
|
"feMerge": ddeSVGFEMergeElement;
|
||||||
|
"feMergeNode": ddeSVGFEMergeNodeElement;
|
||||||
|
"feMorphology": ddeSVGFEMorphologyElement;
|
||||||
|
"feOffset": ddeSVGFEOffsetElement;
|
||||||
|
"fePointLight": ddeSVGFEPointLightElement;
|
||||||
|
"feSpecularLighting": ddeSVGFESpecularLightingElement;
|
||||||
|
"feSpotLight": ddeSVGFESpotLightElement;
|
||||||
|
"feTile": ddeSVGFETileElement;
|
||||||
|
"feTurbulence": ddeSVGFETurbulenceElement;
|
||||||
|
"filter": ddeSVGFilterElement;
|
||||||
|
"foreignObject": ddeSVGForeignObjectElement;
|
||||||
|
"g": ddeSVGGElement;
|
||||||
|
"image": ddeSVGImageElement;
|
||||||
|
"line": ddeSVGLineElement;
|
||||||
|
"linearGradient": ddeSVGLinearGradientElement;
|
||||||
|
"marker": ddeSVGMarkerElement;
|
||||||
|
"mask": ddeSVGMaskElement;
|
||||||
|
"metadata": ddeSVGMetadataElement;
|
||||||
|
"mpath": ddeSVGMPathElement;
|
||||||
|
"path": ddeSVGPathElement;
|
||||||
|
"pattern": ddeSVGPatternElement;
|
||||||
|
"polygon": ddeSVGPolygonElement;
|
||||||
|
"polyline": ddeSVGPolylineElement;
|
||||||
|
"radialGradient": ddeSVGRadialGradientElement;
|
||||||
|
"rect": ddeSVGRectElement;
|
||||||
|
"script": ddeSVGScriptElement;
|
||||||
|
"set": ddeSVGSetElement;
|
||||||
|
"stop": ddeSVGStopElement;
|
||||||
|
"style": ddeSVGStyleElement;
|
||||||
|
"svg": ddeSVGSVGElement;
|
||||||
|
"switch": ddeSVGSwitchElement;
|
||||||
|
"symbol": ddeSVGSymbolElement;
|
||||||
|
"text": ddeSVGTextElement;
|
||||||
|
"textPath": ddeSVGTextPathElement;
|
||||||
|
"title": ddeSVGTitleElement;
|
||||||
|
"tspan": ddeSVGTSpanElement;
|
||||||
|
"use": ddeSVGUseElement;
|
||||||
|
"view": ddeSVGViewElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// editorconfig-checker-disable
|
||||||
|
export interface ddeHTMLAnchorElement extends HTMLAnchorElement {
|
||||||
|
append: ddeAppend<ddeHTMLAnchorElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLAreaElement extends HTMLAreaElement {
|
||||||
|
append: ddeAppend<ddeHTMLAreaElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLAudioElement extends HTMLAudioElement {
|
||||||
|
append: ddeAppend<ddeHTMLAudioElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLBaseElement extends HTMLBaseElement {
|
||||||
|
append: ddeAppend<ddeHTMLBaseElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLQuoteElement extends HTMLQuoteElement {
|
||||||
|
append: ddeAppend<ddeHTMLQuoteElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLBodyElement extends HTMLBodyElement {
|
||||||
|
append: ddeAppend<ddeHTMLBodyElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLBRElement extends HTMLBRElement {
|
||||||
|
append: ddeAppend<ddeHTMLBRElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLButtonElement extends HTMLButtonElement {
|
||||||
|
append: ddeAppend<ddeHTMLButtonElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLCanvasElement extends HTMLCanvasElement {
|
||||||
|
append: ddeAppend<ddeHTMLCanvasElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableCaptionElement extends HTMLTableCaptionElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableCaptionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableColElement extends HTMLTableColElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableColElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableColElement extends HTMLTableColElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableColElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDataElement extends HTMLDataElement {
|
||||||
|
append: ddeAppend<ddeHTMLDataElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDataListElement extends HTMLDataListElement {
|
||||||
|
append: ddeAppend<ddeHTMLDataListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLModElement extends HTMLModElement {
|
||||||
|
append: ddeAppend<ddeHTMLModElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDetailsElement extends HTMLDetailsElement {
|
||||||
|
append: ddeAppend<ddeHTMLDetailsElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDialogElement extends HTMLDialogElement {
|
||||||
|
append: ddeAppend<ddeHTMLDialogElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDivElement extends HTMLDivElement {
|
||||||
|
append: ddeAppend<ddeHTMLDivElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDListElement extends HTMLDListElement {
|
||||||
|
append: ddeAppend<ddeHTMLDListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLEmbedElement extends HTMLEmbedElement {
|
||||||
|
append: ddeAppend<ddeHTMLEmbedElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLFieldSetElement extends HTMLFieldSetElement {
|
||||||
|
append: ddeAppend<ddeHTMLFieldSetElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLFormElement extends HTMLFormElement {
|
||||||
|
append: ddeAppend<ddeHTMLFormElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHeadingElement extends HTMLHeadingElement {
|
||||||
|
append: ddeAppend<ddeHTMLHeadingElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHeadElement extends HTMLHeadElement {
|
||||||
|
append: ddeAppend<ddeHTMLHeadElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHRElement extends HTMLHRElement {
|
||||||
|
append: ddeAppend<ddeHTMLHRElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHtmlElement extends HTMLHtmlElement {
|
||||||
|
append: ddeAppend<ddeHTMLHtmlElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLIFrameElement extends HTMLIFrameElement {
|
||||||
|
append: ddeAppend<ddeHTMLIFrameElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLImageElement extends HTMLImageElement {
|
||||||
|
append: ddeAppend<ddeHTMLImageElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLInputElement extends HTMLInputElement {
|
||||||
|
append: ddeAppend<ddeHTMLInputElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLabelElement extends HTMLLabelElement {
|
||||||
|
append: ddeAppend<ddeHTMLLabelElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLegendElement extends HTMLLegendElement {
|
||||||
|
append: ddeAppend<ddeHTMLLegendElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLIElement extends HTMLLIElement {
|
||||||
|
append: ddeAppend<ddeHTMLLIElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLinkElement extends HTMLLinkElement {
|
||||||
|
append: ddeAppend<ddeHTMLLinkElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMapElement extends HTMLMapElement {
|
||||||
|
append: ddeAppend<ddeHTMLMapElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMenuElement extends HTMLMenuElement {
|
||||||
|
append: ddeAppend<ddeHTMLMenuElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMetaElement extends HTMLMetaElement {
|
||||||
|
append: ddeAppend<ddeHTMLMetaElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMeterElement extends HTMLMeterElement {
|
||||||
|
append: ddeAppend<ddeHTMLMeterElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLObjectElement extends HTMLObjectElement {
|
||||||
|
append: ddeAppend<ddeHTMLObjectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOListElement extends HTMLOListElement {
|
||||||
|
append: ddeAppend<ddeHTMLOListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOptGroupElement extends HTMLOptGroupElement {
|
||||||
|
append: ddeAppend<ddeHTMLOptGroupElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOptionElement extends HTMLOptionElement {
|
||||||
|
append: ddeAppend<ddeHTMLOptionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOutputElement extends HTMLOutputElement {
|
||||||
|
append: ddeAppend<ddeHTMLOutputElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLParagraphElement extends HTMLParagraphElement {
|
||||||
|
append: ddeAppend<ddeHTMLParagraphElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLPictureElement extends HTMLPictureElement {
|
||||||
|
append: ddeAppend<ddeHTMLPictureElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLPreElement extends HTMLPreElement {
|
||||||
|
append: ddeAppend<ddeHTMLPreElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLProgressElement extends HTMLProgressElement {
|
||||||
|
append: ddeAppend<ddeHTMLProgressElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLScriptElement extends HTMLScriptElement {
|
||||||
|
append: ddeAppend<ddeHTMLScriptElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSelectElement extends HTMLSelectElement {
|
||||||
|
append: ddeAppend<ddeHTMLSelectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSlotElement extends HTMLSlotElement {
|
||||||
|
append: ddeAppend<ddeHTMLSlotElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSourceElement extends HTMLSourceElement {
|
||||||
|
append: ddeAppend<ddeHTMLSourceElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSpanElement extends HTMLSpanElement {
|
||||||
|
append: ddeAppend<ddeHTMLSpanElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLStyleElement extends HTMLStyleElement {
|
||||||
|
append: ddeAppend<ddeHTMLStyleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableElement extends HTMLTableElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableSectionElement extends HTMLTableSectionElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableSectionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableCellElement extends HTMLTableCellElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableCellElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTemplateElement extends HTMLTemplateElement {
|
||||||
|
append: ddeAppend<ddeHTMLTemplateElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTextAreaElement extends HTMLTextAreaElement {
|
||||||
|
append: ddeAppend<ddeHTMLTextAreaElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableCellElement extends HTMLTableCellElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableCellElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTimeElement extends HTMLTimeElement {
|
||||||
|
append: ddeAppend<ddeHTMLTimeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTitleElement extends HTMLTitleElement {
|
||||||
|
append: ddeAppend<ddeHTMLTitleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableRowElement extends HTMLTableRowElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableRowElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTrackElement extends HTMLTrackElement {
|
||||||
|
append: ddeAppend<ddeHTMLTrackElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLUListElement extends HTMLUListElement {
|
||||||
|
append: ddeAppend<ddeHTMLUListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLVideoElement extends HTMLVideoElement {
|
||||||
|
append: ddeAppend<ddeHTMLVideoElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAElement extends SVGAElement {
|
||||||
|
append: ddeAppend<ddeSVGAElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAnimateElement extends SVGAnimateElement {
|
||||||
|
append: ddeAppend<ddeSVGAnimateElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAnimateMotionElement extends SVGAnimateMotionElement {
|
||||||
|
append: ddeAppend<ddeSVGAnimateMotionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAnimateTransformElement extends SVGAnimateTransformElement {
|
||||||
|
append: ddeAppend<ddeSVGAnimateTransformElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGCircleElement extends SVGCircleElement {
|
||||||
|
append: ddeAppend<ddeSVGCircleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGClipPathElement extends SVGClipPathElement {
|
||||||
|
append: ddeAppend<ddeSVGClipPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGDefsElement extends SVGDefsElement {
|
||||||
|
append: ddeAppend<ddeSVGDefsElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGDescElement extends SVGDescElement {
|
||||||
|
append: ddeAppend<ddeSVGDescElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGEllipseElement extends SVGEllipseElement {
|
||||||
|
append: ddeAppend<ddeSVGEllipseElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEBlendElement extends SVGFEBlendElement {
|
||||||
|
append: ddeAppend<ddeSVGFEBlendElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEColorMatrixElement extends SVGFEColorMatrixElement {
|
||||||
|
append: ddeAppend<ddeSVGFEColorMatrixElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEComponentTransferElement extends SVGFEComponentTransferElement {
|
||||||
|
append: ddeAppend<ddeSVGFEComponentTransferElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFECompositeElement extends SVGFECompositeElement {
|
||||||
|
append: ddeAppend<ddeSVGFECompositeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEConvolveMatrixElement extends SVGFEConvolveMatrixElement {
|
||||||
|
append: ddeAppend<ddeSVGFEConvolveMatrixElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDiffuseLightingElement extends SVGFEDiffuseLightingElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDiffuseLightingElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDisplacementMapElement extends SVGFEDisplacementMapElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDisplacementMapElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDistantLightElement extends SVGFEDistantLightElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDistantLightElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDropShadowElement extends SVGFEDropShadowElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDropShadowElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFloodElement extends SVGFEFloodElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFloodElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncAElement extends SVGFEFuncAElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncAElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncBElement extends SVGFEFuncBElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncBElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncGElement extends SVGFEFuncGElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncGElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncRElement extends SVGFEFuncRElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncRElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEGaussianBlurElement extends SVGFEGaussianBlurElement {
|
||||||
|
append: ddeAppend<ddeSVGFEGaussianBlurElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEImageElement extends SVGFEImageElement {
|
||||||
|
append: ddeAppend<ddeSVGFEImageElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEMergeElement extends SVGFEMergeElement {
|
||||||
|
append: ddeAppend<ddeSVGFEMergeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEMergeNodeElement extends SVGFEMergeNodeElement {
|
||||||
|
append: ddeAppend<ddeSVGFEMergeNodeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEMorphologyElement extends SVGFEMorphologyElement {
|
||||||
|
append: ddeAppend<ddeSVGFEMorphologyElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEOffsetElement extends SVGFEOffsetElement {
|
||||||
|
append: ddeAppend<ddeSVGFEOffsetElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEPointLightElement extends SVGFEPointLightElement {
|
||||||
|
append: ddeAppend<ddeSVGFEPointLightElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFESpecularLightingElement extends SVGFESpecularLightingElement {
|
||||||
|
append: ddeAppend<ddeSVGFESpecularLightingElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFESpotLightElement extends SVGFESpotLightElement {
|
||||||
|
append: ddeAppend<ddeSVGFESpotLightElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFETileElement extends SVGFETileElement {
|
||||||
|
append: ddeAppend<ddeSVGFETileElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFETurbulenceElement extends SVGFETurbulenceElement {
|
||||||
|
append: ddeAppend<ddeSVGFETurbulenceElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFilterElement extends SVGFilterElement {
|
||||||
|
append: ddeAppend<ddeSVGFilterElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGForeignObjectElement extends SVGForeignObjectElement {
|
||||||
|
append: ddeAppend<ddeSVGForeignObjectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGGElement extends SVGGElement {
|
||||||
|
append: ddeAppend<ddeSVGGElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGImageElement extends SVGImageElement {
|
||||||
|
append: ddeAppend<ddeSVGImageElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGLineElement extends SVGLineElement {
|
||||||
|
append: ddeAppend<ddeSVGLineElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGLinearGradientElement extends SVGLinearGradientElement {
|
||||||
|
append: ddeAppend<ddeSVGLinearGradientElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMarkerElement extends SVGMarkerElement {
|
||||||
|
append: ddeAppend<ddeSVGMarkerElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMaskElement extends SVGMaskElement {
|
||||||
|
append: ddeAppend<ddeSVGMaskElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMetadataElement extends SVGMetadataElement {
|
||||||
|
append: ddeAppend<ddeSVGMetadataElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMPathElement extends SVGMPathElement {
|
||||||
|
append: ddeAppend<ddeSVGMPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPathElement extends SVGPathElement {
|
||||||
|
append: ddeAppend<ddeSVGPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPatternElement extends SVGPatternElement {
|
||||||
|
append: ddeAppend<ddeSVGPatternElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPolygonElement extends SVGPolygonElement {
|
||||||
|
append: ddeAppend<ddeSVGPolygonElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPolylineElement extends SVGPolylineElement {
|
||||||
|
append: ddeAppend<ddeSVGPolylineElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGRadialGradientElement extends SVGRadialGradientElement {
|
||||||
|
append: ddeAppend<ddeSVGRadialGradientElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGRectElement extends SVGRectElement {
|
||||||
|
append: ddeAppend<ddeSVGRectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGScriptElement extends SVGScriptElement {
|
||||||
|
append: ddeAppend<ddeSVGScriptElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSetElement extends SVGSetElement {
|
||||||
|
append: ddeAppend<ddeSVGSetElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGStopElement extends SVGStopElement {
|
||||||
|
append: ddeAppend<ddeSVGStopElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGStyleElement extends SVGStyleElement {
|
||||||
|
append: ddeAppend<ddeSVGStyleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSVGElement extends SVGSVGElement {
|
||||||
|
append: ddeAppend<ddeSVGSVGElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSwitchElement extends SVGSwitchElement {
|
||||||
|
append: ddeAppend<ddeSVGSwitchElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSymbolElement extends SVGSymbolElement {
|
||||||
|
append: ddeAppend<ddeSVGSymbolElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTextElement extends SVGTextElement {
|
||||||
|
append: ddeAppend<ddeSVGTextElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTextPathElement extends SVGTextPathElement {
|
||||||
|
append: ddeAppend<ddeSVGTextPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTitleElement extends SVGTitleElement {
|
||||||
|
append: ddeAppend<ddeSVGTitleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTSpanElement extends SVGTSpanElement {
|
||||||
|
append: ddeAppend<ddeSVGTSpanElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGUseElement extends SVGUseElement {
|
||||||
|
append: ddeAppend<ddeSVGUseElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGViewElement extends SVGViewElement {
|
||||||
|
append: ddeAppend<ddeSVGViewElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
dispatchEvent$1 as dispatchEvent,
|
||||||
|
el as createElement,
|
||||||
|
elNS as createElementNS,
|
||||||
|
};
|
||||||
|
|
||||||
|
export {};
|
7
dist/esm-with-signals.min.js
vendored
7
dist/esm-with-signals.min.js
vendored
File diff suppressed because one or more lines are too long
568
dist/esm.d.min.ts
vendored
568
dist/esm.d.min.ts
vendored
@ -1,568 +0,0 @@
|
|||||||
declare global{ /* ddeSignal */ }
|
|
||||||
type CustomElementTagNameMap= { '#text': Text, '#comment': Comment }
|
|
||||||
type SupportedElement=
|
|
||||||
HTMLElementTagNameMap[keyof HTMLElementTagNameMap]
|
|
||||||
| SVGElementTagNameMap[keyof SVGElementTagNameMap]
|
|
||||||
| MathMLElementTagNameMap[keyof MathMLElementTagNameMap]
|
|
||||||
| CustomElementTagNameMap[keyof CustomElementTagNameMap]
|
|
||||||
declare global {
|
|
||||||
type ddeComponentAttributes= Record<any, any> | undefined;
|
|
||||||
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node>= (element: El)=> any;
|
|
||||||
type ddeString= string | ddeSignal<string>
|
|
||||||
type ddeStringable= ddeString | number | ddeSignal<number>
|
|
||||||
}
|
|
||||||
type PascalCase=
|
|
||||||
`${Capitalize<string>}${string}`;
|
|
||||||
type AttrsModified= {
|
|
||||||
/**
|
|
||||||
* Use string like in HTML (internally uses `*.setAttribute("style", *)`), or object representation (like DOM API).
|
|
||||||
*/
|
|
||||||
style: Partial<CSSStyleDeclaration> | ddeString
|
|
||||||
| Partial<{ [K in keyof CSSStyleDeclaration]: ddeSignal<CSSStyleDeclaration[K]> }>
|
|
||||||
/**
|
|
||||||
* Provide option to add/remove/toggle CSS clasess (index of object) using 1/0/-1.
|
|
||||||
* In fact `el.classList.toggle(class_name)` for `-1` and `el.classList.toggle(class_name, Boolean(...))`
|
|
||||||
* for others.
|
|
||||||
*/
|
|
||||||
classList: Record<string,-1|0|1|boolean|ddeSignal<-1|0|1|boolean>>,
|
|
||||||
/**
|
|
||||||
* Used by the dataset HTML attribute to represent data for custom attributes added to elements.
|
|
||||||
* Values are converted to string (see {@link DOMStringMap}).
|
|
||||||
*
|
|
||||||
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMStringMap)
|
|
||||||
* */
|
|
||||||
dataset: Record<string, ddeStringable>,
|
|
||||||
/**
|
|
||||||
* Sets `aria-*` simiraly to `dataset`
|
|
||||||
* */
|
|
||||||
ariaset: Record<string, ddeString>,
|
|
||||||
} & Record<`=${string}` | `data${PascalCase}` | `aria${PascalCase}`, ddeString>
|
|
||||||
& Record<`.${string}`, any>
|
|
||||||
type _fromElsInterfaces<EL extends SupportedElement>= Omit<EL, keyof AttrsModified>;
|
|
||||||
type IsReadonly<T, K extends keyof T> =
|
|
||||||
T extends { readonly [P in K]: T[K] } ? true : false;
|
|
||||||
/**
|
|
||||||
* Just element attributtes
|
|
||||||
*
|
|
||||||
* In most cases, you can use native propertie such as
|
|
||||||
* [MDN WEB/API/Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) and so on
|
|
||||||
* (e.g. [`Text`](https://developer.mozilla.org/en-US/docs/Web/API/Text)).
|
|
||||||
*
|
|
||||||
* There is added support for `data[A-Z].*`/`aria[A-Z].*` to be converted to the kebab-case alternatives.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
type ElementAttributes<T extends SupportedElement>= Partial<{
|
|
||||||
[K in keyof _fromElsInterfaces<T>]:
|
|
||||||
_fromElsInterfaces<T>[K] extends ((...p: any[])=> any)
|
|
||||||
? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>)=>
|
|
||||||
ddeSignal<ReturnType<_fromElsInterfaces<T>[K]>>)
|
|
||||||
: (IsReadonly<_fromElsInterfaces<T>, K> extends false
|
|
||||||
? _fromElsInterfaces<T>[K] | ddeSignal<_fromElsInterfaces<T>[K]>
|
|
||||||
: ddeStringable)
|
|
||||||
} & AttrsModified> & Record<string, any>;
|
|
||||||
export function classListDeclarative<El extends SupportedElement>(
|
|
||||||
element: El,
|
|
||||||
classList: AttrsModified["classList"]
|
|
||||||
): El
|
|
||||||
export function assign<El extends SupportedElement>(element: El, ...attrs_array: ElementAttributes<El>[]): El
|
|
||||||
export function assignAttribute<El extends SupportedElement, ATT extends keyof ElementAttributes<El>>(
|
|
||||||
element: El,
|
|
||||||
attr: ATT,
|
|
||||||
value: ElementAttributes<El>[ATT]
|
|
||||||
): ElementAttributes<El>[ATT]
|
|
||||||
|
|
||||||
type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap;
|
|
||||||
export namespace el {
|
|
||||||
/**
|
|
||||||
* Creates a marker comment for elements
|
|
||||||
*
|
|
||||||
* @param attrs - Marker attributes
|
|
||||||
* @param [is_open=false] - Whether the marker is open-ended
|
|
||||||
* @returns Comment node marker
|
|
||||||
*/
|
|
||||||
export function mark(
|
|
||||||
attrs: { type: "component"|"reactive"|"later", name?: string, host?: "this"|"parentElement" },
|
|
||||||
is_open?: boolean
|
|
||||||
): Comment;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function el<
|
|
||||||
A extends ddeComponentAttributes,
|
|
||||||
EL extends SupportedElement | ddeDocumentFragment
|
|
||||||
>(
|
|
||||||
component: (attr: A, ...rest: any[])=> EL,
|
|
||||||
attrs?: NoInfer<A>,
|
|
||||||
...addons: ddeElementAddon<EL>[]
|
|
||||||
): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
|
|
||||||
? EL
|
|
||||||
: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement )
|
|
||||||
export function el<
|
|
||||||
A extends { textContent: ddeStringable },
|
|
||||||
EL extends SupportedElement | ddeDocumentFragment
|
|
||||||
>(
|
|
||||||
component: (attr: A, ...rest: any[])=> EL,
|
|
||||||
attrs?: NoInfer<A>["textContent"],
|
|
||||||
...addons: ddeElementAddon<EL>[]
|
|
||||||
): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
|
|
||||||
? EL
|
|
||||||
: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement )
|
|
||||||
export function el<
|
|
||||||
TAG extends keyof ExtendedHTMLElementTagNameMap,
|
|
||||||
>(
|
|
||||||
tag_name: TAG,
|
|
||||||
attrs?: ElementAttributes<ExtendedHTMLElementTagNameMap[NoInfer<TAG>]> | ddeStringable,
|
|
||||||
...addons: ddeElementAddon<
|
|
||||||
ExtendedHTMLElementTagNameMap[NoInfer<TAG>]
|
|
||||||
>[], // TODO: for now addons must have the same element
|
|
||||||
): TAG extends keyof ddeHTMLElementTagNameMap ? ddeHTMLElementTagNameMap[TAG] : ddeHTMLElement
|
|
||||||
export function el(
|
|
||||||
tag_name?: "<>",
|
|
||||||
): ddeDocumentFragment
|
|
||||||
export function el(
|
|
||||||
tag_name: string,
|
|
||||||
attrs?: ElementAttributes<HTMLElement> | ddeStringable,
|
|
||||||
...addons: ddeElementAddon<HTMLElement>[]
|
|
||||||
): ddeHTMLElement
|
|
||||||
export { el as createElement }
|
|
||||||
|
|
||||||
export function elNS(
|
|
||||||
namespace: "http://www.w3.org/2000/svg"
|
|
||||||
): <
|
|
||||||
TAG extends keyof SVGElementTagNameMap & string,
|
|
||||||
EL extends ( TAG extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[TAG] : SVGElement ),
|
|
||||||
>(
|
|
||||||
tag_name: TAG,
|
|
||||||
attrs?: ElementAttributes<NoInfer<EL>> | ddeStringable,
|
|
||||||
...addons: ddeElementAddon<NoInfer<EL>>[]
|
|
||||||
)=> TAG extends keyof ddeSVGElementTagNameMap ? ddeSVGElementTagNameMap[TAG] : ddeSVGElement
|
|
||||||
export function elNS(
|
|
||||||
namespace: "http://www.w3.org/1998/Math/MathML"
|
|
||||||
): <
|
|
||||||
TAG extends keyof MathMLElementTagNameMap & string,
|
|
||||||
EL extends ( TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement ),
|
|
||||||
>(
|
|
||||||
tag_name: TAG,
|
|
||||||
attrs?: ddeStringable | Partial<{
|
|
||||||
[key in keyof EL]: EL[key] | ddeSignal<EL[key]> | string | number | boolean
|
|
||||||
}>,
|
|
||||||
...addons: ddeElementAddon<NoInfer<EL>>[]
|
|
||||||
)=> ddeMathMLElement
|
|
||||||
export function elNS(
|
|
||||||
namespace: string
|
|
||||||
): (
|
|
||||||
tag_name: string,
|
|
||||||
attrs?: string | ddeStringable | Record<string, any>,
|
|
||||||
...addons: ddeElementAddon<SupportedElement>[]
|
|
||||||
)=> SupportedElement
|
|
||||||
export { elNS as createElementNS }
|
|
||||||
|
|
||||||
export function chainableAppend<EL extends SupportedElement>(el: EL): EL;
|
|
||||||
/** Simulate slots for ddeComponents */
|
|
||||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(
|
|
||||||
root: EL,
|
|
||||||
): EL
|
|
||||||
/**
|
|
||||||
* Simulate slots in Custom Elements without using `shadowRoot`.
|
|
||||||
* @param el Custom Element root element
|
|
||||||
* @param body Body of the custom element
|
|
||||||
* */
|
|
||||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(
|
|
||||||
el: HTMLElement,
|
|
||||||
body: EL,
|
|
||||||
): EL
|
|
||||||
|
|
||||||
export function dispatchEvent(name: keyof DocumentEventMap | string, element: SupportedElement):
|
|
||||||
(data?: any)=> void;
|
|
||||||
export function dispatchEvent(name: keyof DocumentEventMap | string, options?: EventInit):
|
|
||||||
(element: SupportedElement, data?: any)=> void;
|
|
||||||
export function dispatchEvent(
|
|
||||||
name: keyof DocumentEventMap | string,
|
|
||||||
options: EventInit | null,
|
|
||||||
element: SupportedElement | (()=> SupportedElement)
|
|
||||||
): (data?: any)=> void;
|
|
||||||
interface On{
|
|
||||||
/** Listens to the DOM event. See {@link Document.addEventListener} */
|
|
||||||
<
|
|
||||||
Event extends keyof DocumentEventMap,
|
|
||||||
EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>,
|
|
||||||
>(
|
|
||||||
type: Event,
|
|
||||||
listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any,
|
|
||||||
options?: AddEventListenerOptions
|
|
||||||
) : EE;
|
|
||||||
<
|
|
||||||
EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>,
|
|
||||||
>(
|
|
||||||
type: string,
|
|
||||||
listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent ) => any,
|
|
||||||
options?: AddEventListenerOptions
|
|
||||||
) : EE;
|
|
||||||
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
|
|
||||||
connected<
|
|
||||||
EE extends ddeElementAddon<SupportedElement>,
|
|
||||||
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
|
|
||||||
>(
|
|
||||||
listener: (this: El, event: CustomEvent<El>) => any,
|
|
||||||
options?: AddEventListenerOptions
|
|
||||||
) : EE;
|
|
||||||
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
|
|
||||||
disconnected<
|
|
||||||
EE extends ddeElementAddon<SupportedElement>,
|
|
||||||
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
|
|
||||||
>(
|
|
||||||
listener: (this: El, event: CustomEvent<void>) => any,
|
|
||||||
options?: AddEventListenerOptions
|
|
||||||
) : EE;
|
|
||||||
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
|
|
||||||
attributeChanged<
|
|
||||||
EE extends ddeElementAddon<SupportedElement>,
|
|
||||||
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
|
|
||||||
>(
|
|
||||||
listener: (this: El, event: CustomEvent<[ string, string ]>) => any,
|
|
||||||
options?: AddEventListenerOptions
|
|
||||||
) : EE;
|
|
||||||
}
|
|
||||||
export const on: On;
|
|
||||||
|
|
||||||
type Scope= {
|
|
||||||
scope: Node | Function | Object,
|
|
||||||
host: ddeElementAddon<any>,
|
|
||||||
custom_element: false | HTMLElement,
|
|
||||||
prevent: boolean
|
|
||||||
};
|
|
||||||
/** Current scope created last time the `el(Function)` was invoke. (Or {@link scope.push}) */
|
|
||||||
export const scope: {
|
|
||||||
current: Scope,
|
|
||||||
/** Stops all automatizations. E. g. signals used as attributes in current scope
|
|
||||||
* registers removing these listeners (and clean signal if no other listeners are detected)
|
|
||||||
* on `disconnected` event. */
|
|
||||||
preventDefault<T extends boolean>(prevent: T): T,
|
|
||||||
/**
|
|
||||||
* This represents reference to the current host element — `scope.host()`.
|
|
||||||
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
|
||||||
* — `scope.host(on.connected(console.log))`.
|
|
||||||
* */
|
|
||||||
host: (...addons: ddeElementAddon<SupportedElement>[])=> HTMLElement,
|
|
||||||
|
|
||||||
state: Scope[],
|
|
||||||
/** Adds new child scope. All attributes are inherited by default. */
|
|
||||||
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>,
|
|
||||||
/** Adds root scope as a child of the current scope. */
|
|
||||||
pushRoot(): ReturnType<Array<Scope>["push"]>,
|
|
||||||
/** Removes last/current child scope. */
|
|
||||||
pop(): ReturnType<Array<Scope>["pop"]>,
|
|
||||||
};
|
|
||||||
|
|
||||||
export function customElementRender<
|
|
||||||
EL extends HTMLElement,
|
|
||||||
P extends any = Record<string, string | ddeSignal<string>>
|
|
||||||
>(
|
|
||||||
target: ShadowRoot | EL,
|
|
||||||
render: (props: P)=> SupportedElement | DocumentFragment,
|
|
||||||
props?: P | ((el: EL)=> P)
|
|
||||||
): EL
|
|
||||||
export function customElementWithDDE<EL extends (new ()=> HTMLElement)>(custom_element: EL): EL
|
|
||||||
export function lifecyclesToEvents<EL extends (new ()=> HTMLElement)>(custom_element: EL): EL
|
|
||||||
export function observedAttributes(custom_element: HTMLElement): Record<string, string>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is used primarly for server side rendering. To be sure that all async operations
|
|
||||||
* are finished before the page is sent to the client.
|
|
||||||
* ```
|
|
||||||
* // on component
|
|
||||||
* function component(){
|
|
||||||
* …
|
|
||||||
* queue(fetch(...).then(...));
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* // building the page
|
|
||||||
* async function build(){
|
|
||||||
* const { component }= await import("./component.js");
|
|
||||||
* document.body.append(el(component));
|
|
||||||
* await queue();
|
|
||||||
* retutn document.body.innerHTML;
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
* */
|
|
||||||
export function queue(promise?: Promise<unknown>): Promise<unknown>;
|
|
||||||
|
|
||||||
/* TypeScript MEH */
|
|
||||||
declare global{
|
|
||||||
type ddeAppend<el>= (...nodes: (Node | string)[])=> el;
|
|
||||||
|
|
||||||
interface ddeDocumentFragment extends DocumentFragment{ append: ddeAppend<ddeDocumentFragment>; }
|
|
||||||
interface ddeHTMLElement extends HTMLElement{ append: ddeAppend<ddeHTMLElement>; }
|
|
||||||
interface ddeSVGElement extends SVGElement{ append: ddeAppend<ddeSVGElement>; }
|
|
||||||
interface ddeMathMLElement extends MathMLElement{ append: ddeAppend<ddeMathMLElement>; }
|
|
||||||
|
|
||||||
interface ddeHTMLElementTagNameMap {
|
|
||||||
"a": ddeHTMLAnchorElement;
|
|
||||||
"area": ddeHTMLAreaElement;
|
|
||||||
"audio": ddeHTMLAudioElement;
|
|
||||||
"base": ddeHTMLBaseElement;
|
|
||||||
"blockquote": ddeHTMLQuoteElement;
|
|
||||||
"body": ddeHTMLBodyElement;
|
|
||||||
"br": ddeHTMLBRElement;
|
|
||||||
"button": ddeHTMLButtonElement;
|
|
||||||
"canvas": ddeHTMLCanvasElement;
|
|
||||||
"caption": ddeHTMLTableCaptionElement;
|
|
||||||
"col": ddeHTMLTableColElement;
|
|
||||||
"colgroup": ddeHTMLTableColElement;
|
|
||||||
"data": ddeHTMLDataElement;
|
|
||||||
"datalist": ddeHTMLDataListElement;
|
|
||||||
"del": ddeHTMLModElement;
|
|
||||||
"details": ddeHTMLDetailsElement;
|
|
||||||
"dialog": ddeHTMLDialogElement;
|
|
||||||
"div": ddeHTMLDivElement;
|
|
||||||
"dl": ddeHTMLDListElement;
|
|
||||||
"embed": ddeHTMLEmbedElement;
|
|
||||||
"fieldset": ddeHTMLFieldSetElement;
|
|
||||||
"form": ddeHTMLFormElement;
|
|
||||||
"h1": ddeHTMLHeadingElement;
|
|
||||||
"h2": ddeHTMLHeadingElement;
|
|
||||||
"h3": ddeHTMLHeadingElement;
|
|
||||||
"h4": ddeHTMLHeadingElement;
|
|
||||||
"h5": ddeHTMLHeadingElement;
|
|
||||||
"h6": ddeHTMLHeadingElement;
|
|
||||||
"head": ddeHTMLHeadElement;
|
|
||||||
"hr": ddeHTMLHRElement;
|
|
||||||
"html": ddeHTMLHtmlElement;
|
|
||||||
"iframe": ddeHTMLIFrameElement;
|
|
||||||
"img": ddeHTMLImageElement;
|
|
||||||
"input": ddeHTMLInputElement;
|
|
||||||
"ins": ddeHTMLModElement;
|
|
||||||
"label": ddeHTMLLabelElement;
|
|
||||||
"legend": ddeHTMLLegendElement;
|
|
||||||
"li": ddeHTMLLIElement;
|
|
||||||
"link": ddeHTMLLinkElement;
|
|
||||||
"map": ddeHTMLMapElement;
|
|
||||||
"menu": ddeHTMLMenuElement;
|
|
||||||
"meta": ddeHTMLMetaElement;
|
|
||||||
"meter": ddeHTMLMeterElement;
|
|
||||||
"object": ddeHTMLObjectElement;
|
|
||||||
"ol": ddeHTMLOListElement;
|
|
||||||
"optgroup": ddeHTMLOptGroupElement;
|
|
||||||
"option": ddeHTMLOptionElement;
|
|
||||||
"output": ddeHTMLOutputElement;
|
|
||||||
"p": ddeHTMLParagraphElement;
|
|
||||||
"picture": ddeHTMLPictureElement;
|
|
||||||
"pre": ddeHTMLPreElement;
|
|
||||||
"progress": ddeHTMLProgressElement;
|
|
||||||
"q": ddeHTMLQuoteElement;
|
|
||||||
"script": ddeHTMLScriptElement;
|
|
||||||
"select": ddeHTMLSelectElement;
|
|
||||||
"slot": ddeHTMLSlotElement;
|
|
||||||
"source": ddeHTMLSourceElement;
|
|
||||||
"span": ddeHTMLSpanElement;
|
|
||||||
"style": ddeHTMLStyleElement;
|
|
||||||
"table": ddeHTMLTableElement;
|
|
||||||
"tbody": ddeHTMLTableSectionElement;
|
|
||||||
"td": ddeHTMLTableCellElement;
|
|
||||||
"template": ddeHTMLTemplateElement;
|
|
||||||
"textarea": ddeHTMLTextAreaElement;
|
|
||||||
"tfoot": ddeHTMLTableSectionElement;
|
|
||||||
"th": ddeHTMLTableCellElement;
|
|
||||||
"thead": ddeHTMLTableSectionElement;
|
|
||||||
"time": ddeHTMLTimeElement;
|
|
||||||
"title": ddeHTMLTitleElement;
|
|
||||||
"tr": ddeHTMLTableRowElement;
|
|
||||||
"track": ddeHTMLTrackElement;
|
|
||||||
"ul": ddeHTMLUListElement;
|
|
||||||
"video": ddeHTMLVideoElement;
|
|
||||||
}
|
|
||||||
interface ddeSVGElementTagNameMap {
|
|
||||||
"a": ddeSVGAElement;
|
|
||||||
"animate": ddeSVGAnimateElement;
|
|
||||||
"animateMotion": ddeSVGAnimateMotionElement;
|
|
||||||
"animateTransform": ddeSVGAnimateTransformElement;
|
|
||||||
"circle": ddeSVGCircleElement;
|
|
||||||
"clipPath": ddeSVGClipPathElement;
|
|
||||||
"defs": ddeSVGDefsElement;
|
|
||||||
"desc": ddeSVGDescElement;
|
|
||||||
"ellipse": ddeSVGEllipseElement;
|
|
||||||
"feBlend": ddeSVGFEBlendElement;
|
|
||||||
"feColorMatrix": ddeSVGFEColorMatrixElement;
|
|
||||||
"feComponentTransfer": ddeSVGFEComponentTransferElement;
|
|
||||||
"feComposite": ddeSVGFECompositeElement;
|
|
||||||
"feConvolveMatrix": ddeSVGFEConvolveMatrixElement;
|
|
||||||
"feDiffuseLighting": ddeSVGFEDiffuseLightingElement;
|
|
||||||
"feDisplacementMap": ddeSVGFEDisplacementMapElement;
|
|
||||||
"feDistantLight": ddeSVGFEDistantLightElement;
|
|
||||||
"feDropShadow": ddeSVGFEDropShadowElement;
|
|
||||||
"feFlood": ddeSVGFEFloodElement;
|
|
||||||
"feFuncA": ddeSVGFEFuncAElement;
|
|
||||||
"feFuncB": ddeSVGFEFuncBElement;
|
|
||||||
"feFuncG": ddeSVGFEFuncGElement;
|
|
||||||
"feFuncR": ddeSVGFEFuncRElement;
|
|
||||||
"feGaussianBlur": ddeSVGFEGaussianBlurElement;
|
|
||||||
"feImage": ddeSVGFEImageElement;
|
|
||||||
"feMerge": ddeSVGFEMergeElement;
|
|
||||||
"feMergeNode": ddeSVGFEMergeNodeElement;
|
|
||||||
"feMorphology": ddeSVGFEMorphologyElement;
|
|
||||||
"feOffset": ddeSVGFEOffsetElement;
|
|
||||||
"fePointLight": ddeSVGFEPointLightElement;
|
|
||||||
"feSpecularLighting": ddeSVGFESpecularLightingElement;
|
|
||||||
"feSpotLight": ddeSVGFESpotLightElement;
|
|
||||||
"feTile": ddeSVGFETileElement;
|
|
||||||
"feTurbulence": ddeSVGFETurbulenceElement;
|
|
||||||
"filter": ddeSVGFilterElement;
|
|
||||||
"foreignObject": ddeSVGForeignObjectElement;
|
|
||||||
"g": ddeSVGGElement;
|
|
||||||
"image": ddeSVGImageElement;
|
|
||||||
"line": ddeSVGLineElement;
|
|
||||||
"linearGradient": ddeSVGLinearGradientElement;
|
|
||||||
"marker": ddeSVGMarkerElement;
|
|
||||||
"mask": ddeSVGMaskElement;
|
|
||||||
"metadata": ddeSVGMetadataElement;
|
|
||||||
"mpath": ddeSVGMPathElement;
|
|
||||||
"path": ddeSVGPathElement;
|
|
||||||
"pattern": ddeSVGPatternElement;
|
|
||||||
"polygon": ddeSVGPolygonElement;
|
|
||||||
"polyline": ddeSVGPolylineElement;
|
|
||||||
"radialGradient": ddeSVGRadialGradientElement;
|
|
||||||
"rect": ddeSVGRectElement;
|
|
||||||
"script": ddeSVGScriptElement;
|
|
||||||
"set": ddeSVGSetElement;
|
|
||||||
"stop": ddeSVGStopElement;
|
|
||||||
"style": ddeSVGStyleElement;
|
|
||||||
"svg": ddeSVGSVGElement;
|
|
||||||
"switch": ddeSVGSwitchElement;
|
|
||||||
"symbol": ddeSVGSymbolElement;
|
|
||||||
"text": ddeSVGTextElement;
|
|
||||||
"textPath": ddeSVGTextPathElement;
|
|
||||||
"title": ddeSVGTitleElement;
|
|
||||||
"tspan": ddeSVGTSpanElement;
|
|
||||||
"use": ddeSVGUseElement;
|
|
||||||
"view": ddeSVGViewElement;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// editorconfig-checker-disable
|
|
||||||
interface ddeHTMLAnchorElement extends HTMLAnchorElement{ append: ddeAppend<ddeHTMLAnchorElement>; }
|
|
||||||
interface ddeHTMLAreaElement extends HTMLAreaElement{ append: ddeAppend<ddeHTMLAreaElement>; }
|
|
||||||
interface ddeHTMLAudioElement extends HTMLAudioElement{ append: ddeAppend<ddeHTMLAudioElement>; }
|
|
||||||
interface ddeHTMLBaseElement extends HTMLBaseElement{ append: ddeAppend<ddeHTMLBaseElement>; }
|
|
||||||
interface ddeHTMLQuoteElement extends HTMLQuoteElement{ append: ddeAppend<ddeHTMLQuoteElement>; }
|
|
||||||
interface ddeHTMLBodyElement extends HTMLBodyElement{ append: ddeAppend<ddeHTMLBodyElement>; }
|
|
||||||
interface ddeHTMLBRElement extends HTMLBRElement{ append: ddeAppend<ddeHTMLBRElement>; }
|
|
||||||
interface ddeHTMLButtonElement extends HTMLButtonElement{ append: ddeAppend<ddeHTMLButtonElement>; }
|
|
||||||
interface ddeHTMLCanvasElement extends HTMLCanvasElement{ append: ddeAppend<ddeHTMLCanvasElement>; }
|
|
||||||
interface ddeHTMLTableCaptionElement extends HTMLTableCaptionElement{ append: ddeAppend<ddeHTMLTableCaptionElement>; }
|
|
||||||
interface ddeHTMLTableColElement extends HTMLTableColElement{ append: ddeAppend<ddeHTMLTableColElement>; }
|
|
||||||
interface ddeHTMLTableColElement extends HTMLTableColElement{ append: ddeAppend<ddeHTMLTableColElement>; }
|
|
||||||
interface ddeHTMLDataElement extends HTMLDataElement{ append: ddeAppend<ddeHTMLDataElement>; }
|
|
||||||
interface ddeHTMLDataListElement extends HTMLDataListElement{ append: ddeAppend<ddeHTMLDataListElement>; }
|
|
||||||
interface ddeHTMLModElement extends HTMLModElement{ append: ddeAppend<ddeHTMLModElement>; }
|
|
||||||
interface ddeHTMLDetailsElement extends HTMLDetailsElement{ append: ddeAppend<ddeHTMLDetailsElement>; }
|
|
||||||
interface ddeHTMLDialogElement extends HTMLDialogElement{ append: ddeAppend<ddeHTMLDialogElement>; }
|
|
||||||
interface ddeHTMLDivElement extends HTMLDivElement{ append: ddeAppend<ddeHTMLDivElement>; }
|
|
||||||
interface ddeHTMLDListElement extends HTMLDListElement{ append: ddeAppend<ddeHTMLDListElement>; }
|
|
||||||
interface ddeHTMLEmbedElement extends HTMLEmbedElement{ append: ddeAppend<ddeHTMLEmbedElement>; }
|
|
||||||
interface ddeHTMLFieldSetElement extends HTMLFieldSetElement{ append: ddeAppend<ddeHTMLFieldSetElement>; }
|
|
||||||
interface ddeHTMLFormElement extends HTMLFormElement{ append: ddeAppend<ddeHTMLFormElement>; }
|
|
||||||
interface ddeHTMLHeadingElement extends HTMLHeadingElement{ append: ddeAppend<ddeHTMLHeadingElement>; }
|
|
||||||
interface ddeHTMLHeadElement extends HTMLHeadElement{ append: ddeAppend<ddeHTMLHeadElement>; }
|
|
||||||
interface ddeHTMLHRElement extends HTMLHRElement{ append: ddeAppend<ddeHTMLHRElement>; }
|
|
||||||
interface ddeHTMLHtmlElement extends HTMLHtmlElement{ append: ddeAppend<ddeHTMLHtmlElement>; }
|
|
||||||
interface ddeHTMLIFrameElement extends HTMLIFrameElement{ append: ddeAppend<ddeHTMLIFrameElement>; }
|
|
||||||
interface ddeHTMLImageElement extends HTMLImageElement{ append: ddeAppend<ddeHTMLImageElement>; }
|
|
||||||
interface ddeHTMLInputElement extends HTMLInputElement{ append: ddeAppend<ddeHTMLInputElement>; }
|
|
||||||
interface ddeHTMLLabelElement extends HTMLLabelElement{ append: ddeAppend<ddeHTMLLabelElement>; }
|
|
||||||
interface ddeHTMLLegendElement extends HTMLLegendElement{ append: ddeAppend<ddeHTMLLegendElement>; }
|
|
||||||
interface ddeHTMLLIElement extends HTMLLIElement{ append: ddeAppend<ddeHTMLLIElement>; }
|
|
||||||
interface ddeHTMLLinkElement extends HTMLLinkElement{ append: ddeAppend<ddeHTMLLinkElement>; }
|
|
||||||
interface ddeHTMLMapElement extends HTMLMapElement{ append: ddeAppend<ddeHTMLMapElement>; }
|
|
||||||
interface ddeHTMLMenuElement extends HTMLMenuElement{ append: ddeAppend<ddeHTMLMenuElement>; }
|
|
||||||
interface ddeHTMLMetaElement extends HTMLMetaElement{ append: ddeAppend<ddeHTMLMetaElement>; }
|
|
||||||
interface ddeHTMLMeterElement extends HTMLMeterElement{ append: ddeAppend<ddeHTMLMeterElement>; }
|
|
||||||
interface ddeHTMLObjectElement extends HTMLObjectElement{ append: ddeAppend<ddeHTMLObjectElement>; }
|
|
||||||
interface ddeHTMLOListElement extends HTMLOListElement{ append: ddeAppend<ddeHTMLOListElement>; }
|
|
||||||
interface ddeHTMLOptGroupElement extends HTMLOptGroupElement{ append: ddeAppend<ddeHTMLOptGroupElement>; }
|
|
||||||
interface ddeHTMLOptionElement extends HTMLOptionElement{ append: ddeAppend<ddeHTMLOptionElement>; }
|
|
||||||
interface ddeHTMLOutputElement extends HTMLOutputElement{ append: ddeAppend<ddeHTMLOutputElement>; }
|
|
||||||
interface ddeHTMLParagraphElement extends HTMLParagraphElement{ append: ddeAppend<ddeHTMLParagraphElement>; }
|
|
||||||
interface ddeHTMLPictureElement extends HTMLPictureElement{ append: ddeAppend<ddeHTMLPictureElement>; }
|
|
||||||
interface ddeHTMLPreElement extends HTMLPreElement{ append: ddeAppend<ddeHTMLPreElement>; }
|
|
||||||
interface ddeHTMLProgressElement extends HTMLProgressElement{ append: ddeAppend<ddeHTMLProgressElement>; }
|
|
||||||
interface ddeHTMLScriptElement extends HTMLScriptElement{ append: ddeAppend<ddeHTMLScriptElement>; }
|
|
||||||
interface ddeHTMLSelectElement extends HTMLSelectElement{ append: ddeAppend<ddeHTMLSelectElement>; }
|
|
||||||
interface ddeHTMLSlotElement extends HTMLSlotElement{ append: ddeAppend<ddeHTMLSlotElement>; }
|
|
||||||
interface ddeHTMLSourceElement extends HTMLSourceElement{ append: ddeAppend<ddeHTMLSourceElement>; }
|
|
||||||
interface ddeHTMLSpanElement extends HTMLSpanElement{ append: ddeAppend<ddeHTMLSpanElement>; }
|
|
||||||
interface ddeHTMLStyleElement extends HTMLStyleElement{ append: ddeAppend<ddeHTMLStyleElement>; }
|
|
||||||
interface ddeHTMLTableElement extends HTMLTableElement{ append: ddeAppend<ddeHTMLTableElement>; }
|
|
||||||
interface ddeHTMLTableSectionElement extends HTMLTableSectionElement{ append: ddeAppend<ddeHTMLTableSectionElement>; }
|
|
||||||
interface ddeHTMLTableCellElement extends HTMLTableCellElement{ append: ddeAppend<ddeHTMLTableCellElement>; }
|
|
||||||
interface ddeHTMLTemplateElement extends HTMLTemplateElement{ append: ddeAppend<ddeHTMLTemplateElement>; }
|
|
||||||
interface ddeHTMLTextAreaElement extends HTMLTextAreaElement{ append: ddeAppend<ddeHTMLTextAreaElement>; }
|
|
||||||
interface ddeHTMLTableCellElement extends HTMLTableCellElement{ append: ddeAppend<ddeHTMLTableCellElement>; }
|
|
||||||
interface ddeHTMLTimeElement extends HTMLTimeElement{ append: ddeAppend<ddeHTMLTimeElement>; }
|
|
||||||
interface ddeHTMLTitleElement extends HTMLTitleElement{ append: ddeAppend<ddeHTMLTitleElement>; }
|
|
||||||
interface ddeHTMLTableRowElement extends HTMLTableRowElement{ append: ddeAppend<ddeHTMLTableRowElement>; }
|
|
||||||
interface ddeHTMLTrackElement extends HTMLTrackElement{ append: ddeAppend<ddeHTMLTrackElement>; }
|
|
||||||
interface ddeHTMLUListElement extends HTMLUListElement{ append: ddeAppend<ddeHTMLUListElement>; }
|
|
||||||
interface ddeHTMLVideoElement extends HTMLVideoElement{ append: ddeAppend<ddeHTMLVideoElement>; }
|
|
||||||
interface ddeSVGAElement extends SVGAElement{ append: ddeAppend<ddeSVGAElement>; }
|
|
||||||
interface ddeSVGAnimateElement extends SVGAnimateElement{ append: ddeAppend<ddeSVGAnimateElement>; }
|
|
||||||
interface ddeSVGAnimateMotionElement extends SVGAnimateMotionElement{ append: ddeAppend<ddeSVGAnimateMotionElement>; }
|
|
||||||
interface ddeSVGAnimateTransformElement extends SVGAnimateTransformElement{ append: ddeAppend<ddeSVGAnimateTransformElement>; }
|
|
||||||
interface ddeSVGCircleElement extends SVGCircleElement{ append: ddeAppend<ddeSVGCircleElement>; }
|
|
||||||
interface ddeSVGClipPathElement extends SVGClipPathElement{ append: ddeAppend<ddeSVGClipPathElement>; }
|
|
||||||
interface ddeSVGDefsElement extends SVGDefsElement{ append: ddeAppend<ddeSVGDefsElement>; }
|
|
||||||
interface ddeSVGDescElement extends SVGDescElement{ append: ddeAppend<ddeSVGDescElement>; }
|
|
||||||
interface ddeSVGEllipseElement extends SVGEllipseElement{ append: ddeAppend<ddeSVGEllipseElement>; }
|
|
||||||
interface ddeSVGFEBlendElement extends SVGFEBlendElement{ append: ddeAppend<ddeSVGFEBlendElement>; }
|
|
||||||
interface ddeSVGFEColorMatrixElement extends SVGFEColorMatrixElement{ append: ddeAppend<ddeSVGFEColorMatrixElement>; }
|
|
||||||
interface ddeSVGFEComponentTransferElement extends SVGFEComponentTransferElement{ append: ddeAppend<ddeSVGFEComponentTransferElement>; }
|
|
||||||
interface ddeSVGFECompositeElement extends SVGFECompositeElement{ append: ddeAppend<ddeSVGFECompositeElement>; }
|
|
||||||
interface ddeSVGFEConvolveMatrixElement extends SVGFEConvolveMatrixElement{ append: ddeAppend<ddeSVGFEConvolveMatrixElement>; }
|
|
||||||
interface ddeSVGFEDiffuseLightingElement extends SVGFEDiffuseLightingElement{ append: ddeAppend<ddeSVGFEDiffuseLightingElement>; }
|
|
||||||
interface ddeSVGFEDisplacementMapElement extends SVGFEDisplacementMapElement{ append: ddeAppend<ddeSVGFEDisplacementMapElement>; }
|
|
||||||
interface ddeSVGFEDistantLightElement extends SVGFEDistantLightElement{ append: ddeAppend<ddeSVGFEDistantLightElement>; }
|
|
||||||
interface ddeSVGFEDropShadowElement extends SVGFEDropShadowElement{ append: ddeAppend<ddeSVGFEDropShadowElement>; }
|
|
||||||
interface ddeSVGFEFloodElement extends SVGFEFloodElement{ append: ddeAppend<ddeSVGFEFloodElement>; }
|
|
||||||
interface ddeSVGFEFuncAElement extends SVGFEFuncAElement{ append: ddeAppend<ddeSVGFEFuncAElement>; }
|
|
||||||
interface ddeSVGFEFuncBElement extends SVGFEFuncBElement{ append: ddeAppend<ddeSVGFEFuncBElement>; }
|
|
||||||
interface ddeSVGFEFuncGElement extends SVGFEFuncGElement{ append: ddeAppend<ddeSVGFEFuncGElement>; }
|
|
||||||
interface ddeSVGFEFuncRElement extends SVGFEFuncRElement{ append: ddeAppend<ddeSVGFEFuncRElement>; }
|
|
||||||
interface ddeSVGFEGaussianBlurElement extends SVGFEGaussianBlurElement{ append: ddeAppend<ddeSVGFEGaussianBlurElement>; }
|
|
||||||
interface ddeSVGFEImageElement extends SVGFEImageElement{ append: ddeAppend<ddeSVGFEImageElement>; }
|
|
||||||
interface ddeSVGFEMergeElement extends SVGFEMergeElement{ append: ddeAppend<ddeSVGFEMergeElement>; }
|
|
||||||
interface ddeSVGFEMergeNodeElement extends SVGFEMergeNodeElement{ append: ddeAppend<ddeSVGFEMergeNodeElement>; }
|
|
||||||
interface ddeSVGFEMorphologyElement extends SVGFEMorphologyElement{ append: ddeAppend<ddeSVGFEMorphologyElement>; }
|
|
||||||
interface ddeSVGFEOffsetElement extends SVGFEOffsetElement{ append: ddeAppend<ddeSVGFEOffsetElement>; }
|
|
||||||
interface ddeSVGFEPointLightElement extends SVGFEPointLightElement{ append: ddeAppend<ddeSVGFEPointLightElement>; }
|
|
||||||
interface ddeSVGFESpecularLightingElement extends SVGFESpecularLightingElement{ append: ddeAppend<ddeSVGFESpecularLightingElement>; }
|
|
||||||
interface ddeSVGFESpotLightElement extends SVGFESpotLightElement{ append: ddeAppend<ddeSVGFESpotLightElement>; }
|
|
||||||
interface ddeSVGFETileElement extends SVGFETileElement{ append: ddeAppend<ddeSVGFETileElement>; }
|
|
||||||
interface ddeSVGFETurbulenceElement extends SVGFETurbulenceElement{ append: ddeAppend<ddeSVGFETurbulenceElement>; }
|
|
||||||
interface ddeSVGFilterElement extends SVGFilterElement{ append: ddeAppend<ddeSVGFilterElement>; }
|
|
||||||
interface ddeSVGForeignObjectElement extends SVGForeignObjectElement{ append: ddeAppend<ddeSVGForeignObjectElement>; }
|
|
||||||
interface ddeSVGGElement extends SVGGElement{ append: ddeAppend<ddeSVGGElement>; }
|
|
||||||
interface ddeSVGImageElement extends SVGImageElement{ append: ddeAppend<ddeSVGImageElement>; }
|
|
||||||
interface ddeSVGLineElement extends SVGLineElement{ append: ddeAppend<ddeSVGLineElement>; }
|
|
||||||
interface ddeSVGLinearGradientElement extends SVGLinearGradientElement{ append: ddeAppend<ddeSVGLinearGradientElement>; }
|
|
||||||
interface ddeSVGMarkerElement extends SVGMarkerElement{ append: ddeAppend<ddeSVGMarkerElement>; }
|
|
||||||
interface ddeSVGMaskElement extends SVGMaskElement{ append: ddeAppend<ddeSVGMaskElement>; }
|
|
||||||
interface ddeSVGMetadataElement extends SVGMetadataElement{ append: ddeAppend<ddeSVGMetadataElement>; }
|
|
||||||
interface ddeSVGMPathElement extends SVGMPathElement{ append: ddeAppend<ddeSVGMPathElement>; }
|
|
||||||
interface ddeSVGPathElement extends SVGPathElement{ append: ddeAppend<ddeSVGPathElement>; }
|
|
||||||
interface ddeSVGPatternElement extends SVGPatternElement{ append: ddeAppend<ddeSVGPatternElement>; }
|
|
||||||
interface ddeSVGPolygonElement extends SVGPolygonElement{ append: ddeAppend<ddeSVGPolygonElement>; }
|
|
||||||
interface ddeSVGPolylineElement extends SVGPolylineElement{ append: ddeAppend<ddeSVGPolylineElement>; }
|
|
||||||
interface ddeSVGRadialGradientElement extends SVGRadialGradientElement{ append: ddeAppend<ddeSVGRadialGradientElement>; }
|
|
||||||
interface ddeSVGRectElement extends SVGRectElement{ append: ddeAppend<ddeSVGRectElement>; }
|
|
||||||
interface ddeSVGScriptElement extends SVGScriptElement{ append: ddeAppend<ddeSVGScriptElement>; }
|
|
||||||
interface ddeSVGSetElement extends SVGSetElement{ append: ddeAppend<ddeSVGSetElement>; }
|
|
||||||
interface ddeSVGStopElement extends SVGStopElement{ append: ddeAppend<ddeSVGStopElement>; }
|
|
||||||
interface ddeSVGStyleElement extends SVGStyleElement{ append: ddeAppend<ddeSVGStyleElement>; }
|
|
||||||
interface ddeSVGSVGElement extends SVGSVGElement{ append: ddeAppend<ddeSVGSVGElement>; }
|
|
||||||
interface ddeSVGSwitchElement extends SVGSwitchElement{ append: ddeAppend<ddeSVGSwitchElement>; }
|
|
||||||
interface ddeSVGSymbolElement extends SVGSymbolElement{ append: ddeAppend<ddeSVGSymbolElement>; }
|
|
||||||
interface ddeSVGTextElement extends SVGTextElement{ append: ddeAppend<ddeSVGTextElement>; }
|
|
||||||
interface ddeSVGTextPathElement extends SVGTextPathElement{ append: ddeAppend<ddeSVGTextPathElement>; }
|
|
||||||
interface ddeSVGTitleElement extends SVGTitleElement{ append: ddeAppend<ddeSVGTitleElement>; }
|
|
||||||
interface ddeSVGTSpanElement extends SVGTSpanElement{ append: ddeAppend<ddeSVGTSpanElement>; }
|
|
||||||
interface ddeSVGUseElement extends SVGUseElement{ append: ddeAppend<ddeSVGUseElement>; }
|
|
||||||
interface ddeSVGViewElement extends SVGViewElement{ append: ddeAppend<ddeSVGViewElement>; }
|
|
||||||
// editorconfig-checker-enable
|
|
965
dist/esm.d.ts
vendored
965
dist/esm.d.ts
vendored
File diff suppressed because it is too large
Load Diff
803
dist/esm.js
vendored
803
dist/esm.js
vendored
@ -1,4 +1,5 @@
|
|||||||
// src/helpers.js
|
// src/helpers.js
|
||||||
|
var hasOwn = (...a) => Object.prototype.hasOwnProperty.call(...a);
|
||||||
function isUndef(value) {
|
function isUndef(value) {
|
||||||
return typeof value === "undefined";
|
return typeof value === "undefined";
|
||||||
}
|
}
|
||||||
@ -8,6 +9,9 @@ function isInstance(obj, cls) {
|
|||||||
function isProtoFrom(obj, cls) {
|
function isProtoFrom(obj, cls) {
|
||||||
return Object.prototype.isPrototypeOf.call(cls, obj);
|
return Object.prototype.isPrototypeOf.call(cls, obj);
|
||||||
}
|
}
|
||||||
|
function oCreate(proto = null, p = {}) {
|
||||||
|
return Object.create(proto, p);
|
||||||
|
}
|
||||||
function oAssign(...o) {
|
function oAssign(...o) {
|
||||||
return Object.assign(...o);
|
return Object.assign(...o);
|
||||||
}
|
}
|
||||||
@ -21,53 +25,18 @@ function onAbort(signal, listener) {
|
|||||||
signal.removeEventListener("abort", listener);
|
signal.removeEventListener("abort", listener);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function observedAttributes(instance, observedAttribute) {
|
function requestIdle() {
|
||||||
const { observedAttributes: observedAttributes3 = [] } = instance.constructor;
|
return new Promise(function(resolve) {
|
||||||
return observedAttributes3.reduce(function(out, name) {
|
(globalThis.requestIdleCallback || requestAnimationFrame)(resolve);
|
||||||
out[kebabToCamel(name)] = observedAttribute(instance, name);
|
});
|
||||||
return out;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
function kebabToCamel(name) {
|
|
||||||
return name.replace(/-./g, (x) => x[1].toUpperCase());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/signals-lib/common.js
|
// src/dom-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: "",
|
||||||
D: globalThis.document,
|
D: globalThis.document,
|
||||||
|
N: globalThis.Node,
|
||||||
F: globalThis.DocumentFragment,
|
F: globalThis.DocumentFragment,
|
||||||
H: globalThis.HTMLElement,
|
H: globalThis.HTMLElement,
|
||||||
S: globalThis.SVGElement,
|
S: globalThis.SVGElement,
|
||||||
@ -88,276 +57,7 @@ var evc = "dde:connected";
|
|||||||
var evd = "dde:disconnected";
|
var evd = "dde:disconnected";
|
||||||
var eva = "dde:attributeChanged";
|
var eva = "dde:attributeChanged";
|
||||||
|
|
||||||
// src/dom.js
|
// src/dom-lib/events-observer.js
|
||||||
function queue(promise) {
|
|
||||||
return enviroment.q(promise);
|
|
||||||
}
|
|
||||||
var scopes = [{
|
|
||||||
get scope() {
|
|
||||||
return enviroment.D.body;
|
|
||||||
},
|
|
||||||
host: (c) => c ? c(enviroment.D.body) : enviroment.D.body,
|
|
||||||
prevent: true
|
|
||||||
}];
|
|
||||||
var 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;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
function append(...els) {
|
|
||||||
this.appendOriginal(...els);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
function chainableAppend(el) {
|
|
||||||
if (el.append === append) return el;
|
|
||||||
el.appendOriginal = el.append;
|
|
||||||
el.append = append;
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
var namespace;
|
|
||||||
function createElement(tag, attributes, ...addons) {
|
|
||||||
const s = signals(this);
|
|
||||||
let scoped = 0;
|
|
||||||
let el, el_host;
|
|
||||||
if (Object(attributes) !== attributes || s.isSignal(attributes))
|
|
||||||
attributes = { textContent: attributes };
|
|
||||||
switch (true) {
|
|
||||||
case typeof tag === "function": {
|
|
||||||
scoped = 1;
|
|
||||||
const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0);
|
|
||||||
scope.push({ scope: tag, host });
|
|
||||||
el = tag(attributes || void 0);
|
|
||||||
const is_fragment = isInstance(el, enviroment.F);
|
|
||||||
if (el.nodeName === "#comment") break;
|
|
||||||
const el_mark = createElement.mark({
|
|
||||||
type: "component",
|
|
||||||
name: tag.name,
|
|
||||||
host: is_fragment ? "this" : "parentElement"
|
|
||||||
});
|
|
||||||
el.prepend(el_mark);
|
|
||||||
if (is_fragment) el_host = el_mark;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case tag === "#text":
|
|
||||||
el = assign.call(this, enviroment.D.createTextNode(""), attributes);
|
|
||||||
break;
|
|
||||||
case (tag === "<>" || !tag):
|
|
||||||
el = assign.call(this, enviroment.D.createDocumentFragment(), attributes);
|
|
||||||
break;
|
|
||||||
case Boolean(namespace):
|
|
||||||
el = assign.call(this, enviroment.D.createElementNS(namespace, tag), attributes);
|
|
||||||
break;
|
|
||||||
case !el:
|
|
||||||
el = assign.call(this, enviroment.D.createElement(tag), attributes);
|
|
||||||
}
|
|
||||||
chainableAppend(el);
|
|
||||||
if (!el_host) el_host = el;
|
|
||||||
addons.forEach((c) => c(el_host));
|
|
||||||
if (scoped) scope.pop();
|
|
||||||
scoped = 2;
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
createElement.mark = function(attrs, is_open = false) {
|
|
||||||
attrs = Object.entries(attrs).map(([n, v]) => n + `="${v}"`).join(" ");
|
|
||||||
const end = is_open ? "" : "/";
|
|
||||||
const out = enviroment.D.createComment(`<dde:mark ${attrs}${enviroment.ssr}${end}>`);
|
|
||||||
if (is_open) out.end = enviroment.D.createComment("</dde:mark>");
|
|
||||||
return out;
|
|
||||||
};
|
|
||||||
function createElementNS(ns) {
|
|
||||||
const _this = this;
|
|
||||||
return function createElementNSCurried(...rest) {
|
|
||||||
namespace = ns;
|
|
||||||
const el = createElement.call(_this, ...rest);
|
|
||||||
namespace = void 0;
|
|
||||||
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 { setDeleteAttr: setDeleteAttr2 } = enviroment;
|
|
||||||
function assign(element, ...attributes) {
|
|
||||||
if (!attributes.length) return element;
|
|
||||||
assign_context.set(element, assignContext(element, this));
|
|
||||||
for (const [key, value] of Object.entries(oAssign({}, ...attributes)))
|
|
||||||
assignAttribute.call(this, element, key, value);
|
|
||||||
assign_context.delete(element);
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
function assignAttribute(element, key, value) {
|
|
||||||
const { setRemoveAttr, s } = assignContext(element, this);
|
|
||||||
const _this = this;
|
|
||||||
value = s.processReactiveAttribute(
|
|
||||||
element,
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
(key2, value2) => assignAttribute.call(_this, element, key2, value2)
|
|
||||||
);
|
|
||||||
const [k] = key;
|
|
||||||
if ("=" === k) return setRemoveAttr(key.slice(1), value);
|
|
||||||
if ("." === k) return setDelete(element, key.slice(1), value);
|
|
||||||
if (/(aria|data)([A-Z])/.test(key)) {
|
|
||||||
key = key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
||||||
return setRemoveAttr(key, value);
|
|
||||||
}
|
|
||||||
if ("className" === key) key = "class";
|
|
||||||
switch (key) {
|
|
||||||
case "xlink:href":
|
|
||||||
return setRemoveAttr(key, value, "http://www.w3.org/1999/xlink");
|
|
||||||
case "textContent":
|
|
||||||
return setDeleteAttr2(element, key, value);
|
|
||||||
case "style":
|
|
||||||
if (typeof value !== "object") break;
|
|
||||||
/* falls through */
|
|
||||||
case "dataset":
|
|
||||||
return forEachEntries(s, key, element, value, setDelete.bind(null, element[key]));
|
|
||||||
case "ariaset":
|
|
||||||
return forEachEntries(s, key, element, value, (key2, val) => setRemoveAttr("aria-" + key2, val));
|
|
||||||
case "classList":
|
|
||||||
return classListDeclarative.call(_this, element, value);
|
|
||||||
}
|
|
||||||
return isPropSetter(element, key) ? setDeleteAttr2(element, key, value) : setRemoveAttr(key, value);
|
|
||||||
}
|
|
||||||
function assignContext(element, _this) {
|
|
||||||
if (assign_context.has(element)) return assign_context.get(element);
|
|
||||||
const is_svg = isInstance(element, enviroment.S);
|
|
||||||
const setRemoveAttr = (is_svg ? setRemoveNS : setRemove).bind(null, element, "Attribute");
|
|
||||||
const s = signals(_this);
|
|
||||||
return { setRemoveAttr, s };
|
|
||||||
}
|
|
||||||
function classListDeclarative(element, toggle) {
|
|
||||||
const s = signals(this);
|
|
||||||
forEachEntries(
|
|
||||||
s,
|
|
||||||
"classList",
|
|
||||||
element,
|
|
||||||
toggle,
|
|
||||||
(class_name, val) => element.classList.toggle(class_name, val === -1 ? void 0 : Boolean(val))
|
|
||||||
);
|
|
||||||
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) {
|
|
||||||
if (!(key in el)) return false;
|
|
||||||
const des = getPropDescriptor(el, key);
|
|
||||||
return !isUndef(des.set);
|
|
||||||
}
|
|
||||||
function getPropDescriptor(p, key) {
|
|
||||||
p = Object.getPrototypeOf(p);
|
|
||||||
if (!p) return {};
|
|
||||||
const des = Object.getOwnPropertyDescriptor(p, key);
|
|
||||||
if (!des) return getPropDescriptor(p, key);
|
|
||||||
return des;
|
|
||||||
}
|
|
||||||
function forEachEntries(s, target, element, obj, cb) {
|
|
||||||
const S = String;
|
|
||||||
if (typeof obj !== "object" || obj === null) return;
|
|
||||||
return Object.entries(obj).forEach(function process([key, val]) {
|
|
||||||
if (!key) return;
|
|
||||||
key = new S(key);
|
|
||||||
key.target = target;
|
|
||||||
val = s.processReactiveAttribute(element, key, val, cb);
|
|
||||||
cb(key, val);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function setRemove(obj, prop, key, val) {
|
|
||||||
return obj[(isUndef(val) ? "remove" : "set") + prop](key, val);
|
|
||||||
}
|
|
||||||
function setRemoveNS(obj, prop, key, val, ns = null) {
|
|
||||||
return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val);
|
|
||||||
}
|
|
||||||
function setDelete(obj, key, val) {
|
|
||||||
Reflect.set(obj, key, val);
|
|
||||||
if (!isUndef(val)) return;
|
|
||||||
return Reflect.deleteProperty(obj, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
// src/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 () => {
|
||||||
@ -468,18 +168,13 @@ function connectionsChangesObserverConstructor() {
|
|||||||
is_observing = false;
|
is_observing = false;
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
}
|
}
|
||||||
function requestIdle() {
|
|
||||||
return new Promise(function(resolve) {
|
|
||||||
(requestIdleCallback || requestAnimationFrame)(resolve);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
async function collectChildren(element) {
|
async function collectChildren(element) {
|
||||||
if (store.size > 30)
|
if (store.size > 30)
|
||||||
await requestIdle();
|
await requestIdle();
|
||||||
const out = [];
|
const out = [];
|
||||||
if (!isInstance(element, Node)) return out;
|
if (!isInstance(element, enviroment.N)) return out;
|
||||||
for (const el of store.keys()) {
|
for (const el of store.keys()) {
|
||||||
if (el === element || !isInstance(el, Node)) continue;
|
if (el === element || !isInstance(el, enviroment.N)) continue;
|
||||||
if (element.contains(el))
|
if (element.contains(el))
|
||||||
out.push(el);
|
out.push(el);
|
||||||
}
|
}
|
||||||
@ -521,8 +216,372 @@ function connectionsChangesObserverConstructor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/customElement.js
|
// src/dom-lib/events.js
|
||||||
function customElementRender(target, render, props = observedAttributes2) {
|
function dispatchEvent(name, options, host) {
|
||||||
|
if (typeof options === "function") {
|
||||||
|
host = options;
|
||||||
|
options = null;
|
||||||
|
}
|
||||||
|
if (!options) options = {};
|
||||||
|
return function dispatch(element, ...d) {
|
||||||
|
if (host) {
|
||||||
|
d.unshift(element);
|
||||||
|
element = typeof host === "function" ? host() : host;
|
||||||
|
}
|
||||||
|
const event = d.length ? new CustomEvent(name, oAssign({ detail: d[0] }, options)) : new Event(name, options);
|
||||||
|
return element.dispatchEvent(event);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function on(event, listener, options) {
|
||||||
|
return function registerElement(element) {
|
||||||
|
element.addEventListener(event, listener, options);
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
on.defer = (fn) => setTimeout.bind(null, fn, 0);
|
||||||
|
var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true });
|
||||||
|
on.connected = function(listener, options) {
|
||||||
|
options = lifeOptions(options);
|
||||||
|
return function registerElement(element) {
|
||||||
|
element.addEventListener(evc, listener, options);
|
||||||
|
if (element[keyLTE]) return element;
|
||||||
|
if (element.isConnected) return element.dispatchEvent(new Event(evc)), element;
|
||||||
|
const c = onAbort(options.signal, () => c_ch_o.offConnected(element, listener));
|
||||||
|
if (c) c_ch_o.onConnected(element, listener);
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
on.disconnected = function(listener, options) {
|
||||||
|
options = lifeOptions(options);
|
||||||
|
return function registerElement(element) {
|
||||||
|
element.addEventListener(evd, listener, options);
|
||||||
|
if (element[keyLTE]) return element;
|
||||||
|
const c = onAbort(options.signal, () => c_ch_o.offDisconnected(element, listener));
|
||||||
|
if (c) c_ch_o.onDisconnected(element, listener);
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/dom-lib/scopes.js
|
||||||
|
var scopes = [{
|
||||||
|
get scope() {
|
||||||
|
return enviroment.D.body;
|
||||||
|
},
|
||||||
|
host: (c) => c ? c(enviroment.D.body) : enviroment.D.body,
|
||||||
|
prevent: true
|
||||||
|
}];
|
||||||
|
var store_abort = /* @__PURE__ */ new WeakMap();
|
||||||
|
var 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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
this.appendOriginal(...els);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
function chainableAppend(el) {
|
||||||
|
if (el.append === append) return el;
|
||||||
|
el.appendOriginal = el.append;
|
||||||
|
el.append = append;
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
var namespace;
|
||||||
|
function createElement(tag, attributes, ...addons) {
|
||||||
|
const s = signals(this);
|
||||||
|
let scoped = 0;
|
||||||
|
let el, el_host;
|
||||||
|
const att_type = typeof attributes;
|
||||||
|
if (att_type === "string" || att_type === "number" || s.isSignal(attributes))
|
||||||
|
attributes = { textContent: attributes };
|
||||||
|
switch (true) {
|
||||||
|
case typeof tag === "function": {
|
||||||
|
scoped = 1;
|
||||||
|
const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0);
|
||||||
|
scope.push({ scope: tag, host });
|
||||||
|
el = /** @type {Element} */
|
||||||
|
tag(attributes || void 0);
|
||||||
|
if (el.nodeName === "#comment") break;
|
||||||
|
const is_fragment = isInstance(el, enviroment.F);
|
||||||
|
const el_mark = createElement.mark({
|
||||||
|
type: "component",
|
||||||
|
name: tag.name,
|
||||||
|
host: is_fragment ? "this" : "parentElement"
|
||||||
|
});
|
||||||
|
el.prepend(el_mark);
|
||||||
|
if (is_fragment) el_host = el_mark;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case tag === "#text":
|
||||||
|
el = assign.call(this, enviroment.D.createTextNode(""), attributes);
|
||||||
|
break;
|
||||||
|
case (tag === "<>" || !tag):
|
||||||
|
el = assign.call(this, enviroment.D.createDocumentFragment(), attributes);
|
||||||
|
break;
|
||||||
|
case Boolean(namespace):
|
||||||
|
el = assign.call(this, enviroment.D.createElementNS(namespace, tag), attributes);
|
||||||
|
break;
|
||||||
|
case !el:
|
||||||
|
el = assign.call(this, enviroment.D.createElement(tag), attributes);
|
||||||
|
}
|
||||||
|
chainableAppend(el);
|
||||||
|
if (!el_host) el_host = el;
|
||||||
|
addons.forEach((c) => c(el_host));
|
||||||
|
if (scoped) scope.pop();
|
||||||
|
scoped = 2;
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
createElement.mark = function(attrs, is_open = false) {
|
||||||
|
attrs = Object.entries(attrs).map(([n, v]) => n + `="${v}"`).join(" ");
|
||||||
|
const end = is_open ? "" : "/";
|
||||||
|
const out = enviroment.D.createComment(`<dde:mark ${attrs}${enviroment.ssr}${end}>`);
|
||||||
|
if (is_open) out.end = enviroment.D.createComment("</dde:mark>");
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
function createElementNS(ns) {
|
||||||
|
const _this = this;
|
||||||
|
return function createElementNSCurried(...rest) {
|
||||||
|
namespace = ns;
|
||||||
|
const el = createElement.call(_this, ...rest);
|
||||||
|
namespace = void 0;
|
||||||
|
return el;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
var assign_context = /* @__PURE__ */ new WeakMap();
|
||||||
|
var { setDeleteAttr: setDeleteAttr2 } = enviroment;
|
||||||
|
function assign(element, ...attributes) {
|
||||||
|
if (!attributes.length) return element;
|
||||||
|
assign_context.set(element, assignContext(element, this));
|
||||||
|
for (const [key, value] of Object.entries(oAssign({}, ...attributes)))
|
||||||
|
assignAttribute.call(this, element, key, value);
|
||||||
|
assign_context.delete(element);
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
function assignAttribute(element, key, value) {
|
||||||
|
const { setRemoveAttr, s } = assignContext(element, this);
|
||||||
|
const _this = this;
|
||||||
|
value = s.processReactiveAttribute(
|
||||||
|
element,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
(key2, value2) => assignAttribute.call(_this, element, key2, value2)
|
||||||
|
);
|
||||||
|
const [k] = key;
|
||||||
|
if ("=" === k) return setRemoveAttr(key.slice(1), value);
|
||||||
|
if ("." === k) return setDelete(element, key.slice(1), value);
|
||||||
|
if (/(aria|data)([A-Z])/.test(key)) {
|
||||||
|
key = key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
||||||
|
return setRemoveAttr(key, value);
|
||||||
|
}
|
||||||
|
if ("className" === key) key = "class";
|
||||||
|
switch (key) {
|
||||||
|
case "xlink:href":
|
||||||
|
return setRemoveAttr(key, value, "http://www.w3.org/1999/xlink");
|
||||||
|
case "textContent":
|
||||||
|
return setDeleteAttr2(element, key, value);
|
||||||
|
case "style":
|
||||||
|
if (typeof value !== "object") break;
|
||||||
|
/* falls through */
|
||||||
|
case "dataset":
|
||||||
|
return forEachEntries(s, key, element, value, setDelete.bind(null, element[key]));
|
||||||
|
case "ariaset":
|
||||||
|
return forEachEntries(s, key, element, value, (key2, val) => setRemoveAttr("aria-" + key2, val));
|
||||||
|
case "classList":
|
||||||
|
return classListDeclarative.call(_this, element, value);
|
||||||
|
}
|
||||||
|
return isPropSetter(element, key) ? setDeleteAttr2(element, key, value) : setRemoveAttr(key, value);
|
||||||
|
}
|
||||||
|
function assignContext(element, _this) {
|
||||||
|
if (assign_context.has(element)) return assign_context.get(element);
|
||||||
|
const is_svg = isInstance(element, enviroment.S);
|
||||||
|
const setRemoveAttr = (is_svg ? setRemoveNS : setRemove).bind(null, element, "Attribute");
|
||||||
|
const s = signals(_this);
|
||||||
|
return { setRemoveAttr, s };
|
||||||
|
}
|
||||||
|
function classListDeclarative(element, toggle) {
|
||||||
|
const s = signals(this);
|
||||||
|
forEachEntries(
|
||||||
|
s,
|
||||||
|
"classList",
|
||||||
|
element,
|
||||||
|
toggle,
|
||||||
|
(class_name, val) => element.classList.toggle(class_name, val === -1 ? void 0 : Boolean(val))
|
||||||
|
);
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
function isPropSetter(el, key) {
|
||||||
|
if (!(key in el)) return false;
|
||||||
|
const des = getPropDescriptor(el, key);
|
||||||
|
return !isUndef(des.set);
|
||||||
|
}
|
||||||
|
function getPropDescriptor(p, key) {
|
||||||
|
p = Object.getPrototypeOf(p);
|
||||||
|
if (!p) return {};
|
||||||
|
const des = Object.getOwnPropertyDescriptor(p, key);
|
||||||
|
if (!des) return getPropDescriptor(p, key);
|
||||||
|
return des;
|
||||||
|
}
|
||||||
|
function forEachEntries(s, target, element, obj, cb) {
|
||||||
|
const S = String;
|
||||||
|
if (typeof obj !== "object" || obj === null) return;
|
||||||
|
return Object.entries(obj).forEach(function process([key, val]) {
|
||||||
|
if (!key) return;
|
||||||
|
key = new S(key);
|
||||||
|
key.target = target;
|
||||||
|
val = s.processReactiveAttribute(element, key, val, cb);
|
||||||
|
cb(key, val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/dom-lib/customElement.js
|
||||||
|
function simulateSlots(element, root = element) {
|
||||||
|
const mark_e = "\xB9\u2070", mark_s = "\u2713";
|
||||||
|
const slots = Object.fromEntries(
|
||||||
|
Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s])
|
||||||
|
);
|
||||||
|
element.append = new Proxy(element.append, {
|
||||||
|
apply(orig, _, els) {
|
||||||
|
if (els[0] === root) return orig.apply(element, els);
|
||||||
|
for (const el of els) {
|
||||||
|
const name = (el.slot || "") + mark_e;
|
||||||
|
try {
|
||||||
|
elementAttribute(el, "remove", "slot");
|
||||||
|
} catch (_error) {
|
||||||
|
}
|
||||||
|
const slot = slots[name];
|
||||||
|
if (!slot) return;
|
||||||
|
if (!slot.name.startsWith(mark_s)) {
|
||||||
|
slot.childNodes.forEach((c) => c.remove());
|
||||||
|
slot.name = mark_s + name;
|
||||||
|
}
|
||||||
|
slot.append(el);
|
||||||
|
}
|
||||||
|
element.append = orig;
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (element !== root) {
|
||||||
|
const els = Array.from(element.childNodes);
|
||||||
|
element.append(...els);
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
function customElementRender(target, render, props = {}) {
|
||||||
const custom_element = target.host || target;
|
const custom_element = target.host || target;
|
||||||
scope.push({
|
scope.push({
|
||||||
scope: custom_element,
|
scope: custom_element,
|
||||||
@ -563,81 +622,40 @@ function wrapMethod(obj, method, apply) {
|
|||||||
obj[method] = new Proxy(obj[method] || (() => {
|
obj[method] = new Proxy(obj[method] || (() => {
|
||||||
}), { apply });
|
}), { apply });
|
||||||
}
|
}
|
||||||
function observedAttributes2(instance) {
|
|
||||||
return observedAttributes(instance, (i, n) => i.getAttribute(n));
|
|
||||||
}
|
|
||||||
|
|
||||||
// src/events.js
|
// src/memo.js
|
||||||
function dispatchEvent(name, options, host) {
|
var memoMark = "__dde_memo";
|
||||||
if (typeof options === "function") {
|
var memo_scope = [];
|
||||||
host = options;
|
function memo(key, generator) {
|
||||||
options = null;
|
if (!memo_scope.length) return generator(key);
|
||||||
|
const k = typeof key === "object" ? JSON.stringify(key) : key;
|
||||||
|
const [{ cache, after }] = memo_scope;
|
||||||
|
return after(k, hasOwn(cache, k) ? cache[k] : generator(key));
|
||||||
}
|
}
|
||||||
if (!options) options = {};
|
memo.isScope = function(obj) {
|
||||||
return function dispatch(element, ...d) {
|
return obj[memoMark];
|
||||||
if (host) {
|
};
|
||||||
d.unshift(element);
|
memo.scope = function memoScopeCreate(fun, { signal, onlyLast } = {}) {
|
||||||
element = typeof host === "function" ? host() : host;
|
let cache = oCreate();
|
||||||
|
function memoScope(...args) {
|
||||||
|
if (signal && signal.aborted)
|
||||||
|
return fun.apply(this, args);
|
||||||
|
let cache_local = onlyLast ? cache : oCreate();
|
||||||
|
memo_scope.unshift({
|
||||||
|
cache,
|
||||||
|
after(key, val) {
|
||||||
|
return cache_local[key] = val;
|
||||||
}
|
}
|
||||||
const event = d.length ? new CustomEvent(name, oAssign({ detail: d[0] }, options)) : new Event(name, options);
|
|
||||||
return element.dispatchEvent(event);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function on(event, listener, options) {
|
|
||||||
return function registerElement(element) {
|
|
||||||
element.addEventListener(event, listener, options);
|
|
||||||
return element;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true });
|
|
||||||
on.connected = function(listener, options) {
|
|
||||||
options = lifeOptions(options);
|
|
||||||
return function registerElement(element) {
|
|
||||||
element.addEventListener(evc, listener, options);
|
|
||||||
if (element[keyLTE]) return element;
|
|
||||||
if (element.isConnected) return element.dispatchEvent(new Event(evc)), element;
|
|
||||||
const c = onAbort(options.signal, () => c_ch_o.offConnected(element, listener));
|
|
||||||
if (c) c_ch_o.onConnected(element, listener);
|
|
||||||
return element;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
on.disconnected = function(listener, options) {
|
|
||||||
options = lifeOptions(options);
|
|
||||||
return function registerElement(element) {
|
|
||||||
element.addEventListener(evd, listener, options);
|
|
||||||
if (element[keyLTE]) return element;
|
|
||||||
const c = onAbort(options.signal, () => c_ch_o.offDisconnected(element, listener));
|
|
||||||
if (c) c_ch_o.onDisconnected(element, listener);
|
|
||||||
return element;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
var store_abort = /* @__PURE__ */ new WeakMap();
|
|
||||||
on.disconnectedAsAbort = function(host) {
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
var els_attribute_store = /* @__PURE__ */ new WeakSet();
|
|
||||||
on.attributeChanged = function(listener, options) {
|
|
||||||
if (typeof options !== "object")
|
|
||||||
options = {};
|
|
||||||
return function registerElement(element) {
|
|
||||||
element.addEventListener(eva, listener, options);
|
|
||||||
if (element[keyLTE] || els_attribute_store.has(element))
|
|
||||||
return element;
|
|
||||||
if (!enviroment.M) return element;
|
|
||||||
const observer = new enviroment.M(function(mutations) {
|
|
||||||
for (const { attributeName, target } of mutations)
|
|
||||||
target.dispatchEvent(
|
|
||||||
new CustomEvent(eva, { detail: [attributeName, target.getAttribute(attributeName)] })
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
const c = onAbort(options.signal, () => observer.disconnect());
|
const out = fun.apply(this, args);
|
||||||
if (c) observer.observe(element, { attributes: true });
|
memo_scope.shift();
|
||||||
return element;
|
cache = cache_local;
|
||||||
};
|
return out;
|
||||||
|
}
|
||||||
|
memoScope[memoMark] = true;
|
||||||
|
memoScope.clear = () => cache = oCreate();
|
||||||
|
if (signal) signal.addEventListener("abort", memoScope.clear);
|
||||||
|
return memoScope;
|
||||||
};
|
};
|
||||||
export {
|
export {
|
||||||
assign,
|
assign,
|
||||||
@ -651,9 +669,8 @@ export {
|
|||||||
dispatchEvent,
|
dispatchEvent,
|
||||||
createElement as el,
|
createElement as el,
|
||||||
createElementNS as elNS,
|
createElementNS as elNS,
|
||||||
elementAttribute,
|
|
||||||
lifecyclesToEvents,
|
lifecyclesToEvents,
|
||||||
observedAttributes2 as observedAttributes,
|
memo,
|
||||||
on,
|
on,
|
||||||
queue,
|
queue,
|
||||||
registerReactivity,
|
registerReactivity,
|
||||||
|
859
dist/esm.min.d.ts
vendored
Normal file
859
dist/esm.min.d.ts
vendored
Normal file
@ -0,0 +1,859 @@
|
|||||||
|
// Generated by dts-bundle-generator v9.5.1
|
||||||
|
|
||||||
|
export interface Signal<V, A> {
|
||||||
|
/** The current value of the signal */
|
||||||
|
get(): V;
|
||||||
|
/** Set new value of the signal */
|
||||||
|
set(value: V, force?: boolean): V;
|
||||||
|
toJSON(): V;
|
||||||
|
valueOf(): V;
|
||||||
|
}
|
||||||
|
export type Action<V> = (this: {
|
||||||
|
value: V;
|
||||||
|
stopPropagation(): void;
|
||||||
|
}, ...a: any[]) => typeof signal._ | void;
|
||||||
|
//type SymbolSignal= Symbol;
|
||||||
|
export type SymbolOnclear = symbol;
|
||||||
|
export type Actions<V> = Record<string | SymbolOnclear, Action<V>>;
|
||||||
|
export type OnListenerOptions = Pick<AddEventListenerOptions, "signal"> & {
|
||||||
|
first_time?: boolean;
|
||||||
|
};
|
||||||
|
export type SElement = Node | Element | DocumentFragment | ddeHTMLElement | ddeSVGElement | ddeDocumentFragment;
|
||||||
|
export interface signal {
|
||||||
|
_: Symbol;
|
||||||
|
/**
|
||||||
|
* Computations signal. This creates a signal which is computed from other signals.
|
||||||
|
* */
|
||||||
|
<V extends () => any>(computation: V): Signal<ReturnType<V>, {}>;
|
||||||
|
/**
|
||||||
|
* Simple example:
|
||||||
|
* ```js
|
||||||
|
* const hello= S("Hello Signal");
|
||||||
|
* ```
|
||||||
|
* …simple todo signal:
|
||||||
|
* ```js
|
||||||
|
* const todos= S([], {
|
||||||
|
* add(v){ this.value.push(S(v)); },
|
||||||
|
* remove(i){ this.value.splice(i, 1); },
|
||||||
|
* [S.symbols.onclear](){ S.clear(...this.value); },
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* …computed signal:
|
||||||
|
* ```js
|
||||||
|
* const name= S("Jan");
|
||||||
|
* const surname= S("Andrle");
|
||||||
|
* const fullname= S(()=> name.get()+" "+surname.get());
|
||||||
|
* ```
|
||||||
|
* @param value Initial signal value. Or function computing value from other signals.
|
||||||
|
* @param actions Use to define actions on the signal. Such as add item to the array.
|
||||||
|
* There is also a reserved function `S.symbol.onclear` which is called when the signal is cleared
|
||||||
|
* by `S.clear`.
|
||||||
|
* */
|
||||||
|
<V, A extends Actions<V>>(value: V, actions?: A): Signal<V, A>;
|
||||||
|
action<S extends Signal<any, Actions<any>>, A extends (S extends Signal<any, infer A> ? A : never), N extends keyof A>(signal: S, name: N, ...params: A[N] extends (...args: infer P) => any ? P : never): void;
|
||||||
|
clear(...signals: Signal<any, any>[]): void;
|
||||||
|
on<T>(signal: Signal<T, any>, onchange: (a: T) => void, options?: OnListenerOptions): void;
|
||||||
|
symbols: {
|
||||||
|
//signal: SymbolSignal;
|
||||||
|
onclear: SymbolOnclear;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Reactive element, which is rendered based on the given signal.
|
||||||
|
* ```js
|
||||||
|
* S.el(signal, value=> value ? el("b", "True") : el("i", "False"));
|
||||||
|
* S.el(listS, list=> list.map(li=> el("li", li)));
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
el<S extends any>(signal: Signal<S, any>, el: (v: S) => SElement | SElement[]): DocumentFragment;
|
||||||
|
observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>;
|
||||||
|
}
|
||||||
|
declare const signal: signal;
|
||||||
|
declare global {
|
||||||
|
type ddeSignal<T, A = {}> = Signal<T, A>;
|
||||||
|
type ddeAction<V> = Action<V>;
|
||||||
|
type ddeActions<V> = Actions<V>;
|
||||||
|
}
|
||||||
|
export type CustomElementTagNameMap = {
|
||||||
|
"#text": Text;
|
||||||
|
"#comment": Comment;
|
||||||
|
};
|
||||||
|
export type SupportedElement = HTMLElementTagNameMap[keyof HTMLElementTagNameMap] | SVGElementTagNameMap[keyof SVGElementTagNameMap] | MathMLElementTagNameMap[keyof MathMLElementTagNameMap] | CustomElementTagNameMap[keyof CustomElementTagNameMap];
|
||||||
|
declare global {
|
||||||
|
type ddeComponentAttributes = Record<any, any> | undefined;
|
||||||
|
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El, ...rest: any) => any;
|
||||||
|
type ddeString = string | Signal<string, {}>;
|
||||||
|
type ddeStringable = ddeString | number | Signal<number, {}>;
|
||||||
|
}
|
||||||
|
export type Host<EL extends SupportedElement> = (...addons: ddeElementAddon<EL>[]) => EL;
|
||||||
|
export type PascalCase = `${Capitalize<string>}${string}`;
|
||||||
|
export type AttrsModified = {
|
||||||
|
/**
|
||||||
|
* Use string like in HTML (internally uses `*.setAttribute("style", *)`), or object representation (like DOM API).
|
||||||
|
*/
|
||||||
|
style: Partial<CSSStyleDeclaration> | ddeString | Partial<{
|
||||||
|
[K in keyof CSSStyleDeclaration]: Signal<CSSStyleDeclaration[K], {}>;
|
||||||
|
}>;
|
||||||
|
/**
|
||||||
|
* Provide option to add/remove/toggle CSS clasess (index of object) using 1/0/-1.
|
||||||
|
* In fact `el.classList.toggle(class_name)` for `-1` and `el.classList.toggle(class_name, Boolean(...))`
|
||||||
|
* for others.
|
||||||
|
*/
|
||||||
|
classList: Record<string, -1 | 0 | 1 | boolean | Signal<-1 | 0 | 1 | boolean, {}>>;
|
||||||
|
/**
|
||||||
|
* Used by the dataset HTML attribute to represent data for custom attributes added to elements.
|
||||||
|
* Values are converted to string (see {@link DOMStringMap}).
|
||||||
|
*
|
||||||
|
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMStringMap)
|
||||||
|
* */
|
||||||
|
dataset: Record<string, ddeStringable>;
|
||||||
|
/**
|
||||||
|
* Sets `aria-*` simiraly to `dataset`
|
||||||
|
* */
|
||||||
|
ariaset: Record<string, ddeString>;
|
||||||
|
} & Record<`=${string}` | `data${PascalCase}` | `aria${PascalCase}`, ddeString> & Record<`.${string}`, any>;
|
||||||
|
export type _fromElsInterfaces<EL extends SupportedElement> = Omit<EL, keyof AttrsModified>;
|
||||||
|
export type IsReadonly<T, K extends keyof T> = T extends {
|
||||||
|
readonly [P in K]: T[K];
|
||||||
|
} ? true : false;
|
||||||
|
/**
|
||||||
|
* Just element attributtes
|
||||||
|
*
|
||||||
|
* In most cases, you can use native propertie such as
|
||||||
|
* [MDN WEB/API/Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) and so on
|
||||||
|
* (e.g. [`Text`](https://developer.mozilla.org/en-US/docs/Web/API/Text)).
|
||||||
|
*
|
||||||
|
* There is added support for `data[A-Z].*`/`aria[A-Z].*` to be converted to the kebab-case alternatives.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export type ElementAttributes<T extends SupportedElement> = Partial<{
|
||||||
|
[K in keyof _fromElsInterfaces<T>]: _fromElsInterfaces<T>[K] extends ((...p: any[]) => any) ? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>) => Signal<ReturnType<_fromElsInterfaces<T>[K]>, {}>) : (IsReadonly<_fromElsInterfaces<T>, K> extends false ? _fromElsInterfaces<T>[K] | Signal<_fromElsInterfaces<T>[K], {}> : ddeStringable);
|
||||||
|
} & AttrsModified> & Record<string, any>;
|
||||||
|
export function classListDeclarative<El extends SupportedElement>(element: El, classList: AttrsModified["classList"]): El;
|
||||||
|
export function assign<El extends SupportedElement>(element: El, ...attrs_array: ElementAttributes<El>[]): El;
|
||||||
|
export function assignAttribute<El extends SupportedElement, ATT extends keyof ElementAttributes<El>>(element: El, attr: ATT, value: ElementAttributes<El>[ATT]): ElementAttributes<El>[ATT];
|
||||||
|
export type ExtendedHTMLElementTagNameMap = HTMLElementTagNameMap & CustomElementTagNameMap;
|
||||||
|
export namespace el {
|
||||||
|
/**
|
||||||
|
* Creates a marker comment for elements
|
||||||
|
*
|
||||||
|
* @param attrs - Marker attributes
|
||||||
|
* @param [is_open=false] - Whether the marker is open-ended
|
||||||
|
* @returns Comment node marker
|
||||||
|
*/
|
||||||
|
export function mark(attrs: {
|
||||||
|
type: "component" | "reactive" | "later";
|
||||||
|
name?: string;
|
||||||
|
host?: "this" | "parentElement";
|
||||||
|
}, is_open?: boolean): Comment;
|
||||||
|
}
|
||||||
|
export function chainableAppend<EL extends SupportedElement>(el: EL): EL | ddeHTMLElement;
|
||||||
|
export function el<A extends ddeComponentAttributes, EL extends SupportedElement | ddeDocumentFragment>(component: (attr: A, ...rest: any[]) => EL, attrs?: NoInfer<A>, ...addons: ddeElementAddon<EL>[]): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? EL : (EL extends ddeDocumentFragment ? EL : ddeHTMLElement);
|
||||||
|
export function el<A extends {
|
||||||
|
textContent: ddeStringable;
|
||||||
|
}, EL extends SupportedElement | ddeDocumentFragment>(component: (attr: A, ...rest: any[]) => EL, attrs?: NoInfer<A>["textContent"], ...addons: ddeElementAddon<EL>[]): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? EL : (EL extends ddeDocumentFragment ? EL : ddeHTMLElement);
|
||||||
|
export function el<TAG extends keyof ExtendedHTMLElementTagNameMap>(tag_name: TAG, attrs?: ElementAttributes<ExtendedHTMLElementTagNameMap[NoInfer<TAG>]> | ddeStringable, ...addons: ddeElementAddon<ExtendedHTMLElementTagNameMap[NoInfer<TAG>]>[]): TAG extends keyof ddeHTMLElementTagNameMap ? ddeHTMLElementTagNameMap[TAG] : ddeHTMLElement;
|
||||||
|
export function el(tag_name?: "<>"): ddeDocumentFragment;
|
||||||
|
export function el(tag_name: string, attrs?: ElementAttributes<HTMLElement> | ddeStringable, ...addons: ddeElementAddon<HTMLElement>[]): ddeHTMLElement;
|
||||||
|
export function elNS(namespace: "http://www.w3.org/2000/svg"): <TAG extends keyof SVGElementTagNameMap & string, EL extends (TAG extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[TAG] : SVGElement)>(tag_name: TAG, attrs?: ElementAttributes<NoInfer<EL>> | ddeStringable, ...addons: ddeElementAddon<NoInfer<EL>>[]) => TAG extends keyof ddeSVGElementTagNameMap ? ddeSVGElementTagNameMap[TAG] : ddeSVGElement;
|
||||||
|
export function elNS(namespace: "http://www.w3.org/1998/Math/MathML"): <TAG extends keyof MathMLElementTagNameMap & string, EL extends (TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement)>(tag_name: TAG, attrs?: ddeStringable | Partial<{
|
||||||
|
[key in keyof EL]: EL[key] | Signal<EL[key], {}> | string | number | boolean;
|
||||||
|
}>, ...addons: ddeElementAddon<NoInfer<EL>>[]) => ddeMathMLElement;
|
||||||
|
export function elNS(namespace: string): (tag_name: string, attrs?: string | ddeStringable | Record<string, any>, ...addons: ddeElementAddon<SupportedElement>[]) => SupportedElement;
|
||||||
|
/** Simulate slots for ddeComponents */
|
||||||
|
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(root: EL): EL;
|
||||||
|
/**
|
||||||
|
* Simulate slots in Custom Elements without using `shadowRoot`.
|
||||||
|
* @param el Custom Element root element
|
||||||
|
* @param body Body of the custom element
|
||||||
|
* */
|
||||||
|
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(el: HTMLElement, body: EL): EL;
|
||||||
|
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, host: Host<SupportedElement>): (data?: any) => void;
|
||||||
|
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?: EventInit): (element: SupportedElement, data?: any) => void;
|
||||||
|
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
|
||||||
|
export interface On {
|
||||||
|
/** Listens to the DOM event. See {@link Document.addEventListener} */
|
||||||
|
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & {
|
||||||
|
target: EL;
|
||||||
|
}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
|
||||||
|
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
|
||||||
|
/** 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>;
|
||||||
|
/** 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>;
|
||||||
|
/**
|
||||||
|
* Fires after the next tick of the Javascript event loop.
|
||||||
|
* This is handy for example to apply some property depending on the element content:
|
||||||
|
* ```js
|
||||||
|
* const selected= "Z";
|
||||||
|
* //...
|
||||||
|
* return el("form").append(
|
||||||
|
* el("select", null, on.defer(e=> e.value=selected)).append(
|
||||||
|
* el("option", { value: "A", textContent: "A" }),
|
||||||
|
* //...
|
||||||
|
* el("option", { value: "Z", textContent: "Z" }),
|
||||||
|
* ),
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>;
|
||||||
|
}
|
||||||
|
export const on: On;
|
||||||
|
export type Scope = {
|
||||||
|
scope: Node | Function | Object;
|
||||||
|
host: Host<SupportedElement>;
|
||||||
|
custom_element: false | HTMLElement;
|
||||||
|
prevent: boolean;
|
||||||
|
};
|
||||||
|
/** Current scope created last time the `el(Function)` was invoke. (Or {@link scope.push}) */
|
||||||
|
export const scope: {
|
||||||
|
current: Scope;
|
||||||
|
/** Stops all automatizations. E. g. signals used as attributes in current scope
|
||||||
|
* registers removing these listeners (and clean signal if no other listeners are detected)
|
||||||
|
* on `disconnected` event. */
|
||||||
|
preventDefault<T extends boolean>(prevent: T): T;
|
||||||
|
/**
|
||||||
|
* This represents reference to the current host element — `scope.host()`.
|
||||||
|
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
||||||
|
* — `scope.host(on.connected(console.log))`.
|
||||||
|
* */
|
||||||
|
host: Host<SupportedElement>;
|
||||||
|
/**
|
||||||
|
* Creates/gets an AbortController that triggers when the element disconnects
|
||||||
|
* */
|
||||||
|
signal: AbortSignal;
|
||||||
|
state: Scope[];
|
||||||
|
/** Adds new child scope. All attributes are inherited by default. */
|
||||||
|
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>;
|
||||||
|
/** Adds root scope as a child of the current scope. */
|
||||||
|
pushRoot(): ReturnType<Array<Scope>["push"]>;
|
||||||
|
/** Removes last/current child scope. */
|
||||||
|
pop(): ReturnType<Array<Scope>["pop"]>;
|
||||||
|
};
|
||||||
|
export function customElementRender<EL extends HTMLElement, P extends any = Record<string, string | Signal<string, {}>>>(target: ShadowRoot | EL, render: (props: P) => SupportedElement | DocumentFragment, props?: P | ((el: EL) => P)): EL;
|
||||||
|
export function customElementWithDDE<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||||
|
export function lifecyclesToEvents<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||||
|
/**
|
||||||
|
* This is used primarly for server side rendering. To be sure that all async operations
|
||||||
|
* are finished before the page is sent to the client.
|
||||||
|
* ```
|
||||||
|
* // on component
|
||||||
|
* function component(){
|
||||||
|
* …
|
||||||
|
* queue(fetch(...).then(...));
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // building the page
|
||||||
|
* async function build(){
|
||||||
|
* const { component }= await import("./component.js");
|
||||||
|
* document.body.append(el(component));
|
||||||
|
* await queue();
|
||||||
|
* retutn document.body.innerHTML;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
export function queue(promise?: Promise<unknown>): Promise<unknown>;
|
||||||
|
/**
|
||||||
|
* Memoization utility for caching DOM elements to improve performance.
|
||||||
|
* Used to prevent unnecessary recreation of elements when rendering lists or complex components.
|
||||||
|
*
|
||||||
|
* @param key - Unique identifier for the element (usually an ID or unique value)
|
||||||
|
* @param generator - Function that creates the element
|
||||||
|
* @returns The cached element if the key exists, otherwise the result of the generator function
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* // Within S.el for list rendering
|
||||||
|
* S.el(itemsSignal, (items, memo) =>
|
||||||
|
* el("ul").append(
|
||||||
|
* ...items.map(item =>
|
||||||
|
* memo(item.id, () => el(ItemComponent, item))
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function memo<T>(key: string | number | object, generator: (key: any) => T): T;
|
||||||
|
/**
|
||||||
|
* Memo namespace containing utility functions for memoization.
|
||||||
|
*/
|
||||||
|
export namespace memo {
|
||||||
|
/**
|
||||||
|
* Checks if an object is a memo scope.
|
||||||
|
* @param obj - The object to check
|
||||||
|
* @returns True if the object is a memo scope
|
||||||
|
*/
|
||||||
|
export function isScope(obj: any): boolean;
|
||||||
|
/**
|
||||||
|
* Creates a memoized function with optional cleanup support.
|
||||||
|
*
|
||||||
|
* @param fun - The function to memoize
|
||||||
|
* @param options - Configuration options
|
||||||
|
* @param options.signal - AbortSignal for cleanup
|
||||||
|
* @param options.onlyLast - When true, only keeps the cache from the most recent call
|
||||||
|
* @returns A memoized version of the function with a .clear() method
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const renderItems = memo.scope(function(items) {
|
||||||
|
* return items.map(item =>
|
||||||
|
* memo(item.id, () => el("div", item.name))
|
||||||
|
* );
|
||||||
|
* }, {
|
||||||
|
* signal: controller.signal,
|
||||||
|
* onlyLast: true
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function scope<F extends Function>(fun: F, options?: {
|
||||||
|
signal?: AbortSignal;
|
||||||
|
onlyLast?: boolean;
|
||||||
|
}): F & {
|
||||||
|
clear: () => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/* TypeScript MEH */
|
||||||
|
declare global {
|
||||||
|
type ddeAppend<el> = (...nodes: (Node | string)[]) => el;
|
||||||
|
interface ddeDocumentFragment extends DocumentFragment {
|
||||||
|
append: ddeAppend<ddeDocumentFragment>;
|
||||||
|
}
|
||||||
|
interface ddeHTMLElement extends HTMLElement {
|
||||||
|
append: ddeAppend<ddeHTMLElement>;
|
||||||
|
}
|
||||||
|
interface ddeSVGElement extends SVGElement {
|
||||||
|
append: ddeAppend<ddeSVGElement>;
|
||||||
|
}
|
||||||
|
interface ddeMathMLElement extends MathMLElement {
|
||||||
|
append: ddeAppend<ddeMathMLElement>;
|
||||||
|
}
|
||||||
|
interface ddeHTMLElementTagNameMap {
|
||||||
|
"a": ddeHTMLAnchorElement;
|
||||||
|
"area": ddeHTMLAreaElement;
|
||||||
|
"audio": ddeHTMLAudioElement;
|
||||||
|
"base": ddeHTMLBaseElement;
|
||||||
|
"blockquote": ddeHTMLQuoteElement;
|
||||||
|
"body": ddeHTMLBodyElement;
|
||||||
|
"br": ddeHTMLBRElement;
|
||||||
|
"button": ddeHTMLButtonElement;
|
||||||
|
"canvas": ddeHTMLCanvasElement;
|
||||||
|
"caption": ddeHTMLTableCaptionElement;
|
||||||
|
"col": ddeHTMLTableColElement;
|
||||||
|
"colgroup": ddeHTMLTableColElement;
|
||||||
|
"data": ddeHTMLDataElement;
|
||||||
|
"datalist": ddeHTMLDataListElement;
|
||||||
|
"del": ddeHTMLModElement;
|
||||||
|
"details": ddeHTMLDetailsElement;
|
||||||
|
"dialog": ddeHTMLDialogElement;
|
||||||
|
"div": ddeHTMLDivElement;
|
||||||
|
"dl": ddeHTMLDListElement;
|
||||||
|
"embed": ddeHTMLEmbedElement;
|
||||||
|
"fieldset": ddeHTMLFieldSetElement;
|
||||||
|
"form": ddeHTMLFormElement;
|
||||||
|
"h1": ddeHTMLHeadingElement;
|
||||||
|
"h2": ddeHTMLHeadingElement;
|
||||||
|
"h3": ddeHTMLHeadingElement;
|
||||||
|
"h4": ddeHTMLHeadingElement;
|
||||||
|
"h5": ddeHTMLHeadingElement;
|
||||||
|
"h6": ddeHTMLHeadingElement;
|
||||||
|
"head": ddeHTMLHeadElement;
|
||||||
|
"hr": ddeHTMLHRElement;
|
||||||
|
"html": ddeHTMLHtmlElement;
|
||||||
|
"iframe": ddeHTMLIFrameElement;
|
||||||
|
"img": ddeHTMLImageElement;
|
||||||
|
"input": ddeHTMLInputElement;
|
||||||
|
"ins": ddeHTMLModElement;
|
||||||
|
"label": ddeHTMLLabelElement;
|
||||||
|
"legend": ddeHTMLLegendElement;
|
||||||
|
"li": ddeHTMLLIElement;
|
||||||
|
"link": ddeHTMLLinkElement;
|
||||||
|
"map": ddeHTMLMapElement;
|
||||||
|
"menu": ddeHTMLMenuElement;
|
||||||
|
"meta": ddeHTMLMetaElement;
|
||||||
|
"meter": ddeHTMLMeterElement;
|
||||||
|
"object": ddeHTMLObjectElement;
|
||||||
|
"ol": ddeHTMLOListElement;
|
||||||
|
"optgroup": ddeHTMLOptGroupElement;
|
||||||
|
"option": ddeHTMLOptionElement;
|
||||||
|
"output": ddeHTMLOutputElement;
|
||||||
|
"p": ddeHTMLParagraphElement;
|
||||||
|
"picture": ddeHTMLPictureElement;
|
||||||
|
"pre": ddeHTMLPreElement;
|
||||||
|
"progress": ddeHTMLProgressElement;
|
||||||
|
"q": ddeHTMLQuoteElement;
|
||||||
|
"script": ddeHTMLScriptElement;
|
||||||
|
"select": ddeHTMLSelectElement;
|
||||||
|
"slot": ddeHTMLSlotElement;
|
||||||
|
"source": ddeHTMLSourceElement;
|
||||||
|
"span": ddeHTMLSpanElement;
|
||||||
|
"style": ddeHTMLStyleElement;
|
||||||
|
"table": ddeHTMLTableElement;
|
||||||
|
"tbody": ddeHTMLTableSectionElement;
|
||||||
|
"td": ddeHTMLTableCellElement;
|
||||||
|
"template": ddeHTMLTemplateElement;
|
||||||
|
"textarea": ddeHTMLTextAreaElement;
|
||||||
|
"tfoot": ddeHTMLTableSectionElement;
|
||||||
|
"th": ddeHTMLTableCellElement;
|
||||||
|
"thead": ddeHTMLTableSectionElement;
|
||||||
|
"time": ddeHTMLTimeElement;
|
||||||
|
"title": ddeHTMLTitleElement;
|
||||||
|
"tr": ddeHTMLTableRowElement;
|
||||||
|
"track": ddeHTMLTrackElement;
|
||||||
|
"ul": ddeHTMLUListElement;
|
||||||
|
"video": ddeHTMLVideoElement;
|
||||||
|
}
|
||||||
|
interface ddeSVGElementTagNameMap {
|
||||||
|
"a": ddeSVGAElement;
|
||||||
|
"animate": ddeSVGAnimateElement;
|
||||||
|
"animateMotion": ddeSVGAnimateMotionElement;
|
||||||
|
"animateTransform": ddeSVGAnimateTransformElement;
|
||||||
|
"circle": ddeSVGCircleElement;
|
||||||
|
"clipPath": ddeSVGClipPathElement;
|
||||||
|
"defs": ddeSVGDefsElement;
|
||||||
|
"desc": ddeSVGDescElement;
|
||||||
|
"ellipse": ddeSVGEllipseElement;
|
||||||
|
"feBlend": ddeSVGFEBlendElement;
|
||||||
|
"feColorMatrix": ddeSVGFEColorMatrixElement;
|
||||||
|
"feComponentTransfer": ddeSVGFEComponentTransferElement;
|
||||||
|
"feComposite": ddeSVGFECompositeElement;
|
||||||
|
"feConvolveMatrix": ddeSVGFEConvolveMatrixElement;
|
||||||
|
"feDiffuseLighting": ddeSVGFEDiffuseLightingElement;
|
||||||
|
"feDisplacementMap": ddeSVGFEDisplacementMapElement;
|
||||||
|
"feDistantLight": ddeSVGFEDistantLightElement;
|
||||||
|
"feDropShadow": ddeSVGFEDropShadowElement;
|
||||||
|
"feFlood": ddeSVGFEFloodElement;
|
||||||
|
"feFuncA": ddeSVGFEFuncAElement;
|
||||||
|
"feFuncB": ddeSVGFEFuncBElement;
|
||||||
|
"feFuncG": ddeSVGFEFuncGElement;
|
||||||
|
"feFuncR": ddeSVGFEFuncRElement;
|
||||||
|
"feGaussianBlur": ddeSVGFEGaussianBlurElement;
|
||||||
|
"feImage": ddeSVGFEImageElement;
|
||||||
|
"feMerge": ddeSVGFEMergeElement;
|
||||||
|
"feMergeNode": ddeSVGFEMergeNodeElement;
|
||||||
|
"feMorphology": ddeSVGFEMorphologyElement;
|
||||||
|
"feOffset": ddeSVGFEOffsetElement;
|
||||||
|
"fePointLight": ddeSVGFEPointLightElement;
|
||||||
|
"feSpecularLighting": ddeSVGFESpecularLightingElement;
|
||||||
|
"feSpotLight": ddeSVGFESpotLightElement;
|
||||||
|
"feTile": ddeSVGFETileElement;
|
||||||
|
"feTurbulence": ddeSVGFETurbulenceElement;
|
||||||
|
"filter": ddeSVGFilterElement;
|
||||||
|
"foreignObject": ddeSVGForeignObjectElement;
|
||||||
|
"g": ddeSVGGElement;
|
||||||
|
"image": ddeSVGImageElement;
|
||||||
|
"line": ddeSVGLineElement;
|
||||||
|
"linearGradient": ddeSVGLinearGradientElement;
|
||||||
|
"marker": ddeSVGMarkerElement;
|
||||||
|
"mask": ddeSVGMaskElement;
|
||||||
|
"metadata": ddeSVGMetadataElement;
|
||||||
|
"mpath": ddeSVGMPathElement;
|
||||||
|
"path": ddeSVGPathElement;
|
||||||
|
"pattern": ddeSVGPatternElement;
|
||||||
|
"polygon": ddeSVGPolygonElement;
|
||||||
|
"polyline": ddeSVGPolylineElement;
|
||||||
|
"radialGradient": ddeSVGRadialGradientElement;
|
||||||
|
"rect": ddeSVGRectElement;
|
||||||
|
"script": ddeSVGScriptElement;
|
||||||
|
"set": ddeSVGSetElement;
|
||||||
|
"stop": ddeSVGStopElement;
|
||||||
|
"style": ddeSVGStyleElement;
|
||||||
|
"svg": ddeSVGSVGElement;
|
||||||
|
"switch": ddeSVGSwitchElement;
|
||||||
|
"symbol": ddeSVGSymbolElement;
|
||||||
|
"text": ddeSVGTextElement;
|
||||||
|
"textPath": ddeSVGTextPathElement;
|
||||||
|
"title": ddeSVGTitleElement;
|
||||||
|
"tspan": ddeSVGTSpanElement;
|
||||||
|
"use": ddeSVGUseElement;
|
||||||
|
"view": ddeSVGViewElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// editorconfig-checker-disable
|
||||||
|
export interface ddeHTMLAnchorElement extends HTMLAnchorElement {
|
||||||
|
append: ddeAppend<ddeHTMLAnchorElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLAreaElement extends HTMLAreaElement {
|
||||||
|
append: ddeAppend<ddeHTMLAreaElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLAudioElement extends HTMLAudioElement {
|
||||||
|
append: ddeAppend<ddeHTMLAudioElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLBaseElement extends HTMLBaseElement {
|
||||||
|
append: ddeAppend<ddeHTMLBaseElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLQuoteElement extends HTMLQuoteElement {
|
||||||
|
append: ddeAppend<ddeHTMLQuoteElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLBodyElement extends HTMLBodyElement {
|
||||||
|
append: ddeAppend<ddeHTMLBodyElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLBRElement extends HTMLBRElement {
|
||||||
|
append: ddeAppend<ddeHTMLBRElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLButtonElement extends HTMLButtonElement {
|
||||||
|
append: ddeAppend<ddeHTMLButtonElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLCanvasElement extends HTMLCanvasElement {
|
||||||
|
append: ddeAppend<ddeHTMLCanvasElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableCaptionElement extends HTMLTableCaptionElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableCaptionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableColElement extends HTMLTableColElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableColElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableColElement extends HTMLTableColElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableColElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDataElement extends HTMLDataElement {
|
||||||
|
append: ddeAppend<ddeHTMLDataElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDataListElement extends HTMLDataListElement {
|
||||||
|
append: ddeAppend<ddeHTMLDataListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLModElement extends HTMLModElement {
|
||||||
|
append: ddeAppend<ddeHTMLModElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDetailsElement extends HTMLDetailsElement {
|
||||||
|
append: ddeAppend<ddeHTMLDetailsElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDialogElement extends HTMLDialogElement {
|
||||||
|
append: ddeAppend<ddeHTMLDialogElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDivElement extends HTMLDivElement {
|
||||||
|
append: ddeAppend<ddeHTMLDivElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDListElement extends HTMLDListElement {
|
||||||
|
append: ddeAppend<ddeHTMLDListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLEmbedElement extends HTMLEmbedElement {
|
||||||
|
append: ddeAppend<ddeHTMLEmbedElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLFieldSetElement extends HTMLFieldSetElement {
|
||||||
|
append: ddeAppend<ddeHTMLFieldSetElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLFormElement extends HTMLFormElement {
|
||||||
|
append: ddeAppend<ddeHTMLFormElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHeadingElement extends HTMLHeadingElement {
|
||||||
|
append: ddeAppend<ddeHTMLHeadingElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHeadElement extends HTMLHeadElement {
|
||||||
|
append: ddeAppend<ddeHTMLHeadElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHRElement extends HTMLHRElement {
|
||||||
|
append: ddeAppend<ddeHTMLHRElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHtmlElement extends HTMLHtmlElement {
|
||||||
|
append: ddeAppend<ddeHTMLHtmlElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLIFrameElement extends HTMLIFrameElement {
|
||||||
|
append: ddeAppend<ddeHTMLIFrameElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLImageElement extends HTMLImageElement {
|
||||||
|
append: ddeAppend<ddeHTMLImageElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLInputElement extends HTMLInputElement {
|
||||||
|
append: ddeAppend<ddeHTMLInputElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLabelElement extends HTMLLabelElement {
|
||||||
|
append: ddeAppend<ddeHTMLLabelElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLegendElement extends HTMLLegendElement {
|
||||||
|
append: ddeAppend<ddeHTMLLegendElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLIElement extends HTMLLIElement {
|
||||||
|
append: ddeAppend<ddeHTMLLIElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLinkElement extends HTMLLinkElement {
|
||||||
|
append: ddeAppend<ddeHTMLLinkElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMapElement extends HTMLMapElement {
|
||||||
|
append: ddeAppend<ddeHTMLMapElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMenuElement extends HTMLMenuElement {
|
||||||
|
append: ddeAppend<ddeHTMLMenuElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMetaElement extends HTMLMetaElement {
|
||||||
|
append: ddeAppend<ddeHTMLMetaElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMeterElement extends HTMLMeterElement {
|
||||||
|
append: ddeAppend<ddeHTMLMeterElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLObjectElement extends HTMLObjectElement {
|
||||||
|
append: ddeAppend<ddeHTMLObjectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOListElement extends HTMLOListElement {
|
||||||
|
append: ddeAppend<ddeHTMLOListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOptGroupElement extends HTMLOptGroupElement {
|
||||||
|
append: ddeAppend<ddeHTMLOptGroupElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOptionElement extends HTMLOptionElement {
|
||||||
|
append: ddeAppend<ddeHTMLOptionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOutputElement extends HTMLOutputElement {
|
||||||
|
append: ddeAppend<ddeHTMLOutputElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLParagraphElement extends HTMLParagraphElement {
|
||||||
|
append: ddeAppend<ddeHTMLParagraphElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLPictureElement extends HTMLPictureElement {
|
||||||
|
append: ddeAppend<ddeHTMLPictureElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLPreElement extends HTMLPreElement {
|
||||||
|
append: ddeAppend<ddeHTMLPreElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLProgressElement extends HTMLProgressElement {
|
||||||
|
append: ddeAppend<ddeHTMLProgressElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLScriptElement extends HTMLScriptElement {
|
||||||
|
append: ddeAppend<ddeHTMLScriptElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSelectElement extends HTMLSelectElement {
|
||||||
|
append: ddeAppend<ddeHTMLSelectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSlotElement extends HTMLSlotElement {
|
||||||
|
append: ddeAppend<ddeHTMLSlotElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSourceElement extends HTMLSourceElement {
|
||||||
|
append: ddeAppend<ddeHTMLSourceElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSpanElement extends HTMLSpanElement {
|
||||||
|
append: ddeAppend<ddeHTMLSpanElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLStyleElement extends HTMLStyleElement {
|
||||||
|
append: ddeAppend<ddeHTMLStyleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableElement extends HTMLTableElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableSectionElement extends HTMLTableSectionElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableSectionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableCellElement extends HTMLTableCellElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableCellElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTemplateElement extends HTMLTemplateElement {
|
||||||
|
append: ddeAppend<ddeHTMLTemplateElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTextAreaElement extends HTMLTextAreaElement {
|
||||||
|
append: ddeAppend<ddeHTMLTextAreaElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableCellElement extends HTMLTableCellElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableCellElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTimeElement extends HTMLTimeElement {
|
||||||
|
append: ddeAppend<ddeHTMLTimeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTitleElement extends HTMLTitleElement {
|
||||||
|
append: ddeAppend<ddeHTMLTitleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableRowElement extends HTMLTableRowElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableRowElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTrackElement extends HTMLTrackElement {
|
||||||
|
append: ddeAppend<ddeHTMLTrackElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLUListElement extends HTMLUListElement {
|
||||||
|
append: ddeAppend<ddeHTMLUListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLVideoElement extends HTMLVideoElement {
|
||||||
|
append: ddeAppend<ddeHTMLVideoElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAElement extends SVGAElement {
|
||||||
|
append: ddeAppend<ddeSVGAElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAnimateElement extends SVGAnimateElement {
|
||||||
|
append: ddeAppend<ddeSVGAnimateElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAnimateMotionElement extends SVGAnimateMotionElement {
|
||||||
|
append: ddeAppend<ddeSVGAnimateMotionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAnimateTransformElement extends SVGAnimateTransformElement {
|
||||||
|
append: ddeAppend<ddeSVGAnimateTransformElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGCircleElement extends SVGCircleElement {
|
||||||
|
append: ddeAppend<ddeSVGCircleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGClipPathElement extends SVGClipPathElement {
|
||||||
|
append: ddeAppend<ddeSVGClipPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGDefsElement extends SVGDefsElement {
|
||||||
|
append: ddeAppend<ddeSVGDefsElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGDescElement extends SVGDescElement {
|
||||||
|
append: ddeAppend<ddeSVGDescElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGEllipseElement extends SVGEllipseElement {
|
||||||
|
append: ddeAppend<ddeSVGEllipseElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEBlendElement extends SVGFEBlendElement {
|
||||||
|
append: ddeAppend<ddeSVGFEBlendElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEColorMatrixElement extends SVGFEColorMatrixElement {
|
||||||
|
append: ddeAppend<ddeSVGFEColorMatrixElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEComponentTransferElement extends SVGFEComponentTransferElement {
|
||||||
|
append: ddeAppend<ddeSVGFEComponentTransferElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFECompositeElement extends SVGFECompositeElement {
|
||||||
|
append: ddeAppend<ddeSVGFECompositeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEConvolveMatrixElement extends SVGFEConvolveMatrixElement {
|
||||||
|
append: ddeAppend<ddeSVGFEConvolveMatrixElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDiffuseLightingElement extends SVGFEDiffuseLightingElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDiffuseLightingElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDisplacementMapElement extends SVGFEDisplacementMapElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDisplacementMapElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDistantLightElement extends SVGFEDistantLightElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDistantLightElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDropShadowElement extends SVGFEDropShadowElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDropShadowElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFloodElement extends SVGFEFloodElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFloodElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncAElement extends SVGFEFuncAElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncAElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncBElement extends SVGFEFuncBElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncBElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncGElement extends SVGFEFuncGElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncGElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncRElement extends SVGFEFuncRElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncRElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEGaussianBlurElement extends SVGFEGaussianBlurElement {
|
||||||
|
append: ddeAppend<ddeSVGFEGaussianBlurElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEImageElement extends SVGFEImageElement {
|
||||||
|
append: ddeAppend<ddeSVGFEImageElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEMergeElement extends SVGFEMergeElement {
|
||||||
|
append: ddeAppend<ddeSVGFEMergeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEMergeNodeElement extends SVGFEMergeNodeElement {
|
||||||
|
append: ddeAppend<ddeSVGFEMergeNodeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEMorphologyElement extends SVGFEMorphologyElement {
|
||||||
|
append: ddeAppend<ddeSVGFEMorphologyElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEOffsetElement extends SVGFEOffsetElement {
|
||||||
|
append: ddeAppend<ddeSVGFEOffsetElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEPointLightElement extends SVGFEPointLightElement {
|
||||||
|
append: ddeAppend<ddeSVGFEPointLightElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFESpecularLightingElement extends SVGFESpecularLightingElement {
|
||||||
|
append: ddeAppend<ddeSVGFESpecularLightingElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFESpotLightElement extends SVGFESpotLightElement {
|
||||||
|
append: ddeAppend<ddeSVGFESpotLightElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFETileElement extends SVGFETileElement {
|
||||||
|
append: ddeAppend<ddeSVGFETileElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFETurbulenceElement extends SVGFETurbulenceElement {
|
||||||
|
append: ddeAppend<ddeSVGFETurbulenceElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFilterElement extends SVGFilterElement {
|
||||||
|
append: ddeAppend<ddeSVGFilterElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGForeignObjectElement extends SVGForeignObjectElement {
|
||||||
|
append: ddeAppend<ddeSVGForeignObjectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGGElement extends SVGGElement {
|
||||||
|
append: ddeAppend<ddeSVGGElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGImageElement extends SVGImageElement {
|
||||||
|
append: ddeAppend<ddeSVGImageElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGLineElement extends SVGLineElement {
|
||||||
|
append: ddeAppend<ddeSVGLineElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGLinearGradientElement extends SVGLinearGradientElement {
|
||||||
|
append: ddeAppend<ddeSVGLinearGradientElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMarkerElement extends SVGMarkerElement {
|
||||||
|
append: ddeAppend<ddeSVGMarkerElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMaskElement extends SVGMaskElement {
|
||||||
|
append: ddeAppend<ddeSVGMaskElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMetadataElement extends SVGMetadataElement {
|
||||||
|
append: ddeAppend<ddeSVGMetadataElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMPathElement extends SVGMPathElement {
|
||||||
|
append: ddeAppend<ddeSVGMPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPathElement extends SVGPathElement {
|
||||||
|
append: ddeAppend<ddeSVGPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPatternElement extends SVGPatternElement {
|
||||||
|
append: ddeAppend<ddeSVGPatternElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPolygonElement extends SVGPolygonElement {
|
||||||
|
append: ddeAppend<ddeSVGPolygonElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPolylineElement extends SVGPolylineElement {
|
||||||
|
append: ddeAppend<ddeSVGPolylineElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGRadialGradientElement extends SVGRadialGradientElement {
|
||||||
|
append: ddeAppend<ddeSVGRadialGradientElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGRectElement extends SVGRectElement {
|
||||||
|
append: ddeAppend<ddeSVGRectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGScriptElement extends SVGScriptElement {
|
||||||
|
append: ddeAppend<ddeSVGScriptElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSetElement extends SVGSetElement {
|
||||||
|
append: ddeAppend<ddeSVGSetElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGStopElement extends SVGStopElement {
|
||||||
|
append: ddeAppend<ddeSVGStopElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGStyleElement extends SVGStyleElement {
|
||||||
|
append: ddeAppend<ddeSVGStyleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSVGElement extends SVGSVGElement {
|
||||||
|
append: ddeAppend<ddeSVGSVGElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSwitchElement extends SVGSwitchElement {
|
||||||
|
append: ddeAppend<ddeSVGSwitchElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSymbolElement extends SVGSymbolElement {
|
||||||
|
append: ddeAppend<ddeSVGSymbolElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTextElement extends SVGTextElement {
|
||||||
|
append: ddeAppend<ddeSVGTextElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTextPathElement extends SVGTextPathElement {
|
||||||
|
append: ddeAppend<ddeSVGTextPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTitleElement extends SVGTitleElement {
|
||||||
|
append: ddeAppend<ddeSVGTitleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTSpanElement extends SVGTSpanElement {
|
||||||
|
append: ddeAppend<ddeSVGTSpanElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGUseElement extends SVGUseElement {
|
||||||
|
append: ddeAppend<ddeSVGUseElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGViewElement extends SVGViewElement {
|
||||||
|
append: ddeAppend<ddeSVGViewElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
dispatchEvent$1 as dispatchEvent,
|
||||||
|
el as createElement,
|
||||||
|
elNS as createElementNS,
|
||||||
|
};
|
||||||
|
|
||||||
|
export {};
|
2
dist/esm.min.js
vendored
2
dist/esm.min.js
vendored
File diff suppressed because one or more lines are too long
862
dist/iife-with-signals.d.ts
vendored
Normal file
862
dist/iife-with-signals.d.ts
vendored
Normal file
@ -0,0 +1,862 @@
|
|||||||
|
// Generated by dts-bundle-generator v9.5.1
|
||||||
|
|
||||||
|
export interface Signal<V, A> {
|
||||||
|
/** The current value of the signal */
|
||||||
|
get(): V;
|
||||||
|
/** Set new value of the signal */
|
||||||
|
set(value: V, force?: boolean): V;
|
||||||
|
toJSON(): V;
|
||||||
|
valueOf(): V;
|
||||||
|
}
|
||||||
|
export type Action<V> = (this: {
|
||||||
|
value: V;
|
||||||
|
stopPropagation(): void;
|
||||||
|
}, ...a: any[]) => typeof signal._ | void;
|
||||||
|
//type SymbolSignal= Symbol;
|
||||||
|
export type SymbolOnclear = symbol;
|
||||||
|
export type Actions<V> = Record<string | SymbolOnclear, Action<V>>;
|
||||||
|
export type OnListenerOptions = Pick<AddEventListenerOptions, "signal"> & {
|
||||||
|
first_time?: boolean;
|
||||||
|
};
|
||||||
|
export type SElement = Node | Element | DocumentFragment | ddeHTMLElement | ddeSVGElement | ddeDocumentFragment;
|
||||||
|
export interface signal {
|
||||||
|
_: Symbol;
|
||||||
|
/**
|
||||||
|
* Computations signal. This creates a signal which is computed from other signals.
|
||||||
|
* */
|
||||||
|
<V extends () => any>(computation: V): Signal<ReturnType<V>, {}>;
|
||||||
|
/**
|
||||||
|
* Simple example:
|
||||||
|
* ```js
|
||||||
|
* const hello= S("Hello Signal");
|
||||||
|
* ```
|
||||||
|
* …simple todo signal:
|
||||||
|
* ```js
|
||||||
|
* const todos= S([], {
|
||||||
|
* add(v){ this.value.push(S(v)); },
|
||||||
|
* remove(i){ this.value.splice(i, 1); },
|
||||||
|
* [S.symbols.onclear](){ S.clear(...this.value); },
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* …computed signal:
|
||||||
|
* ```js
|
||||||
|
* const name= S("Jan");
|
||||||
|
* const surname= S("Andrle");
|
||||||
|
* const fullname= S(()=> name.get()+" "+surname.get());
|
||||||
|
* ```
|
||||||
|
* @param value Initial signal value. Or function computing value from other signals.
|
||||||
|
* @param actions Use to define actions on the signal. Such as add item to the array.
|
||||||
|
* There is also a reserved function `S.symbol.onclear` which is called when the signal is cleared
|
||||||
|
* by `S.clear`.
|
||||||
|
* */
|
||||||
|
<V, A extends Actions<V>>(value: V, actions?: A): Signal<V, A>;
|
||||||
|
action<S extends Signal<any, Actions<any>>, A extends (S extends Signal<any, infer A> ? A : never), N extends keyof A>(signal: S, name: N, ...params: A[N] extends (...args: infer P) => any ? P : never): void;
|
||||||
|
clear(...signals: Signal<any, any>[]): void;
|
||||||
|
on<T>(signal: Signal<T, any>, onchange: (a: T) => void, options?: OnListenerOptions): void;
|
||||||
|
symbols: {
|
||||||
|
//signal: SymbolSignal;
|
||||||
|
onclear: SymbolOnclear;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Reactive element, which is rendered based on the given signal.
|
||||||
|
* ```js
|
||||||
|
* S.el(signal, value=> value ? el("b", "True") : el("i", "False"));
|
||||||
|
* S.el(listS, list=> list.map(li=> el("li", li)));
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
el<S extends any>(signal: Signal<S, any>, el: (v: S) => SElement | SElement[]): DocumentFragment;
|
||||||
|
observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>;
|
||||||
|
}
|
||||||
|
export const signal: signal;
|
||||||
|
export const S: signal;
|
||||||
|
declare global {
|
||||||
|
type ddeSignal<T, A = {}> = Signal<T, A>;
|
||||||
|
type ddeAction<V> = Action<V>;
|
||||||
|
type ddeActions<V> = Actions<V>;
|
||||||
|
}
|
||||||
|
export type CustomElementTagNameMap = {
|
||||||
|
"#text": Text;
|
||||||
|
"#comment": Comment;
|
||||||
|
};
|
||||||
|
export type SupportedElement = HTMLElementTagNameMap[keyof HTMLElementTagNameMap] | SVGElementTagNameMap[keyof SVGElementTagNameMap] | MathMLElementTagNameMap[keyof MathMLElementTagNameMap] | CustomElementTagNameMap[keyof CustomElementTagNameMap];
|
||||||
|
declare global {
|
||||||
|
type ddeComponentAttributes = Record<any, any> | undefined;
|
||||||
|
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El, ...rest: any) => any;
|
||||||
|
type ddeString = string | Signal<string, {}>;
|
||||||
|
type ddeStringable = ddeString | number | Signal<number, {}>;
|
||||||
|
}
|
||||||
|
export type Host<EL extends SupportedElement> = (...addons: ddeElementAddon<EL>[]) => EL;
|
||||||
|
export type PascalCase = `${Capitalize<string>}${string}`;
|
||||||
|
export type AttrsModified = {
|
||||||
|
/**
|
||||||
|
* Use string like in HTML (internally uses `*.setAttribute("style", *)`), or object representation (like DOM API).
|
||||||
|
*/
|
||||||
|
style: Partial<CSSStyleDeclaration> | ddeString | Partial<{
|
||||||
|
[K in keyof CSSStyleDeclaration]: Signal<CSSStyleDeclaration[K], {}>;
|
||||||
|
}>;
|
||||||
|
/**
|
||||||
|
* Provide option to add/remove/toggle CSS clasess (index of object) using 1/0/-1.
|
||||||
|
* In fact `el.classList.toggle(class_name)` for `-1` and `el.classList.toggle(class_name, Boolean(...))`
|
||||||
|
* for others.
|
||||||
|
*/
|
||||||
|
classList: Record<string, -1 | 0 | 1 | boolean | Signal<-1 | 0 | 1 | boolean, {}>>;
|
||||||
|
/**
|
||||||
|
* Used by the dataset HTML attribute to represent data for custom attributes added to elements.
|
||||||
|
* Values are converted to string (see {@link DOMStringMap}).
|
||||||
|
*
|
||||||
|
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMStringMap)
|
||||||
|
* */
|
||||||
|
dataset: Record<string, ddeStringable>;
|
||||||
|
/**
|
||||||
|
* Sets `aria-*` simiraly to `dataset`
|
||||||
|
* */
|
||||||
|
ariaset: Record<string, ddeString>;
|
||||||
|
} & Record<`=${string}` | `data${PascalCase}` | `aria${PascalCase}`, ddeString> & Record<`.${string}`, any>;
|
||||||
|
export type _fromElsInterfaces<EL extends SupportedElement> = Omit<EL, keyof AttrsModified>;
|
||||||
|
export type IsReadonly<T, K extends keyof T> = T extends {
|
||||||
|
readonly [P in K]: T[K];
|
||||||
|
} ? true : false;
|
||||||
|
/**
|
||||||
|
* Just element attributtes
|
||||||
|
*
|
||||||
|
* In most cases, you can use native propertie such as
|
||||||
|
* [MDN WEB/API/Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) and so on
|
||||||
|
* (e.g. [`Text`](https://developer.mozilla.org/en-US/docs/Web/API/Text)).
|
||||||
|
*
|
||||||
|
* There is added support for `data[A-Z].*`/`aria[A-Z].*` to be converted to the kebab-case alternatives.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export type ElementAttributes<T extends SupportedElement> = Partial<{
|
||||||
|
[K in keyof _fromElsInterfaces<T>]: _fromElsInterfaces<T>[K] extends ((...p: any[]) => any) ? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>) => Signal<ReturnType<_fromElsInterfaces<T>[K]>, {}>) : (IsReadonly<_fromElsInterfaces<T>, K> extends false ? _fromElsInterfaces<T>[K] | Signal<_fromElsInterfaces<T>[K], {}> : ddeStringable);
|
||||||
|
} & AttrsModified> & Record<string, any>;
|
||||||
|
export function classListDeclarative<El extends SupportedElement>(element: El, classList: AttrsModified["classList"]): El;
|
||||||
|
export function assign<El extends SupportedElement>(element: El, ...attrs_array: ElementAttributes<El>[]): El;
|
||||||
|
export function assignAttribute<El extends SupportedElement, ATT extends keyof ElementAttributes<El>>(element: El, attr: ATT, value: ElementAttributes<El>[ATT]): ElementAttributes<El>[ATT];
|
||||||
|
export type ExtendedHTMLElementTagNameMap = HTMLElementTagNameMap & CustomElementTagNameMap;
|
||||||
|
export namespace el {
|
||||||
|
/**
|
||||||
|
* Creates a marker comment for elements
|
||||||
|
*
|
||||||
|
* @param attrs - Marker attributes
|
||||||
|
* @param [is_open=false] - Whether the marker is open-ended
|
||||||
|
* @returns Comment node marker
|
||||||
|
*/
|
||||||
|
export function mark(attrs: {
|
||||||
|
type: "component" | "reactive" | "later";
|
||||||
|
name?: string;
|
||||||
|
host?: "this" | "parentElement";
|
||||||
|
}, is_open?: boolean): Comment;
|
||||||
|
}
|
||||||
|
export function chainableAppend<EL extends SupportedElement>(el: EL): EL | ddeHTMLElement;
|
||||||
|
export function el<A extends ddeComponentAttributes, EL extends SupportedElement | ddeDocumentFragment>(component: (attr: A, ...rest: any[]) => EL, attrs?: NoInfer<A>, ...addons: ddeElementAddon<EL>[]): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? EL : (EL extends ddeDocumentFragment ? EL : ddeHTMLElement);
|
||||||
|
export function el<A extends {
|
||||||
|
textContent: ddeStringable;
|
||||||
|
}, EL extends SupportedElement | ddeDocumentFragment>(component: (attr: A, ...rest: any[]) => EL, attrs?: NoInfer<A>["textContent"], ...addons: ddeElementAddon<EL>[]): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? EL : (EL extends ddeDocumentFragment ? EL : ddeHTMLElement);
|
||||||
|
export function el<TAG extends keyof ExtendedHTMLElementTagNameMap>(tag_name: TAG, attrs?: ElementAttributes<ExtendedHTMLElementTagNameMap[NoInfer<TAG>]> | ddeStringable, ...addons: ddeElementAddon<ExtendedHTMLElementTagNameMap[NoInfer<TAG>]>[]): TAG extends keyof ddeHTMLElementTagNameMap ? ddeHTMLElementTagNameMap[TAG] : ddeHTMLElement;
|
||||||
|
export function el(tag_name?: "<>"): ddeDocumentFragment;
|
||||||
|
export function el(tag_name: string, attrs?: ElementAttributes<HTMLElement> | ddeStringable, ...addons: ddeElementAddon<HTMLElement>[]): ddeHTMLElement;
|
||||||
|
export function elNS(namespace: "http://www.w3.org/2000/svg"): <TAG extends keyof SVGElementTagNameMap & string, EL extends (TAG extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[TAG] : SVGElement)>(tag_name: TAG, attrs?: ElementAttributes<NoInfer<EL>> | ddeStringable, ...addons: ddeElementAddon<NoInfer<EL>>[]) => TAG extends keyof ddeSVGElementTagNameMap ? ddeSVGElementTagNameMap[TAG] : ddeSVGElement;
|
||||||
|
export function elNS(namespace: "http://www.w3.org/1998/Math/MathML"): <TAG extends keyof MathMLElementTagNameMap & string, EL extends (TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement)>(tag_name: TAG, attrs?: ddeStringable | Partial<{
|
||||||
|
[key in keyof EL]: EL[key] | Signal<EL[key], {}> | string | number | boolean;
|
||||||
|
}>, ...addons: ddeElementAddon<NoInfer<EL>>[]) => ddeMathMLElement;
|
||||||
|
export function elNS(namespace: string): (tag_name: string, attrs?: string | ddeStringable | Record<string, any>, ...addons: ddeElementAddon<SupportedElement>[]) => SupportedElement;
|
||||||
|
/** Simulate slots for ddeComponents */
|
||||||
|
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(root: EL): EL;
|
||||||
|
/**
|
||||||
|
* Simulate slots in Custom Elements without using `shadowRoot`.
|
||||||
|
* @param el Custom Element root element
|
||||||
|
* @param body Body of the custom element
|
||||||
|
* */
|
||||||
|
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(el: HTMLElement, body: EL): EL;
|
||||||
|
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, host: Host<SupportedElement>): (data?: any) => void;
|
||||||
|
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?: EventInit): (element: SupportedElement, data?: any) => void;
|
||||||
|
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
|
||||||
|
export interface On {
|
||||||
|
/** Listens to the DOM event. See {@link Document.addEventListener} */
|
||||||
|
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & {
|
||||||
|
target: EL;
|
||||||
|
}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
|
||||||
|
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
|
||||||
|
/** 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>;
|
||||||
|
/** 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>;
|
||||||
|
/**
|
||||||
|
* Fires after the next tick of the Javascript event loop.
|
||||||
|
* This is handy for example to apply some property depending on the element content:
|
||||||
|
* ```js
|
||||||
|
* const selected= "Z";
|
||||||
|
* //...
|
||||||
|
* return el("form").append(
|
||||||
|
* el("select", null, on.defer(e=> e.value=selected)).append(
|
||||||
|
* el("option", { value: "A", textContent: "A" }),
|
||||||
|
* //...
|
||||||
|
* el("option", { value: "Z", textContent: "Z" }),
|
||||||
|
* ),
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>;
|
||||||
|
}
|
||||||
|
export const on: On;
|
||||||
|
export type Scope = {
|
||||||
|
scope: Node | Function | Object;
|
||||||
|
host: Host<SupportedElement>;
|
||||||
|
custom_element: false | HTMLElement;
|
||||||
|
prevent: boolean;
|
||||||
|
};
|
||||||
|
/** Current scope created last time the `el(Function)` was invoke. (Or {@link scope.push}) */
|
||||||
|
export const scope: {
|
||||||
|
current: Scope;
|
||||||
|
/** Stops all automatizations. E. g. signals used as attributes in current scope
|
||||||
|
* registers removing these listeners (and clean signal if no other listeners are detected)
|
||||||
|
* on `disconnected` event. */
|
||||||
|
preventDefault<T extends boolean>(prevent: T): T;
|
||||||
|
/**
|
||||||
|
* This represents reference to the current host element — `scope.host()`.
|
||||||
|
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
||||||
|
* — `scope.host(on.connected(console.log))`.
|
||||||
|
* */
|
||||||
|
host: Host<SupportedElement>;
|
||||||
|
/**
|
||||||
|
* Creates/gets an AbortController that triggers when the element disconnects
|
||||||
|
* */
|
||||||
|
signal: AbortSignal;
|
||||||
|
state: Scope[];
|
||||||
|
/** Adds new child scope. All attributes are inherited by default. */
|
||||||
|
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>;
|
||||||
|
/** Adds root scope as a child of the current scope. */
|
||||||
|
pushRoot(): ReturnType<Array<Scope>["push"]>;
|
||||||
|
/** Removes last/current child scope. */
|
||||||
|
pop(): ReturnType<Array<Scope>["pop"]>;
|
||||||
|
};
|
||||||
|
export function customElementRender<EL extends HTMLElement, P extends any = Record<string, string | Signal<string, {}>>>(target: ShadowRoot | EL, render: (props: P) => SupportedElement | DocumentFragment, props?: P | ((el: EL) => P)): EL;
|
||||||
|
export function customElementWithDDE<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||||
|
export function lifecyclesToEvents<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||||
|
/**
|
||||||
|
* This is used primarly for server side rendering. To be sure that all async operations
|
||||||
|
* are finished before the page is sent to the client.
|
||||||
|
* ```
|
||||||
|
* // on component
|
||||||
|
* function component(){
|
||||||
|
* …
|
||||||
|
* queue(fetch(...).then(...));
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // building the page
|
||||||
|
* async function build(){
|
||||||
|
* const { component }= await import("./component.js");
|
||||||
|
* document.body.append(el(component));
|
||||||
|
* await queue();
|
||||||
|
* retutn document.body.innerHTML;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
export function queue(promise?: Promise<unknown>): Promise<unknown>;
|
||||||
|
/**
|
||||||
|
* Memoization utility for caching DOM elements to improve performance.
|
||||||
|
* Used to prevent unnecessary recreation of elements when rendering lists or complex components.
|
||||||
|
*
|
||||||
|
* @param key - Unique identifier for the element (usually an ID or unique value)
|
||||||
|
* @param generator - Function that creates the element
|
||||||
|
* @returns The cached element if the key exists, otherwise the result of the generator function
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* // Within S.el for list rendering
|
||||||
|
* S.el(itemsSignal, (items, memo) =>
|
||||||
|
* el("ul").append(
|
||||||
|
* ...items.map(item =>
|
||||||
|
* memo(item.id, () => el(ItemComponent, item))
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function memo<T>(key: string | number | object, generator: (key: any) => T): T;
|
||||||
|
/**
|
||||||
|
* Memo namespace containing utility functions for memoization.
|
||||||
|
*/
|
||||||
|
export namespace memo {
|
||||||
|
/**
|
||||||
|
* Checks if an object is a memo scope.
|
||||||
|
* @param obj - The object to check
|
||||||
|
* @returns True if the object is a memo scope
|
||||||
|
*/
|
||||||
|
export function isScope(obj: any): boolean;
|
||||||
|
/**
|
||||||
|
* Creates a memoized function with optional cleanup support.
|
||||||
|
*
|
||||||
|
* @param fun - The function to memoize
|
||||||
|
* @param options - Configuration options
|
||||||
|
* @param options.signal - AbortSignal for cleanup
|
||||||
|
* @param options.onlyLast - When true, only keeps the cache from the most recent call
|
||||||
|
* @returns A memoized version of the function with a .clear() method
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const renderItems = memo.scope(function(items) {
|
||||||
|
* return items.map(item =>
|
||||||
|
* memo(item.id, () => el("div", item.name))
|
||||||
|
* );
|
||||||
|
* }, {
|
||||||
|
* signal: controller.signal,
|
||||||
|
* onlyLast: true
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function scope<F extends Function>(fun: F, options?: {
|
||||||
|
signal?: AbortSignal;
|
||||||
|
onlyLast?: boolean;
|
||||||
|
}): F & {
|
||||||
|
clear: () => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/* TypeScript MEH */
|
||||||
|
declare global {
|
||||||
|
type ddeAppend<el> = (...nodes: (Node | string)[]) => el;
|
||||||
|
interface ddeDocumentFragment extends DocumentFragment {
|
||||||
|
append: ddeAppend<ddeDocumentFragment>;
|
||||||
|
}
|
||||||
|
interface ddeHTMLElement extends HTMLElement {
|
||||||
|
append: ddeAppend<ddeHTMLElement>;
|
||||||
|
}
|
||||||
|
interface ddeSVGElement extends SVGElement {
|
||||||
|
append: ddeAppend<ddeSVGElement>;
|
||||||
|
}
|
||||||
|
interface ddeMathMLElement extends MathMLElement {
|
||||||
|
append: ddeAppend<ddeMathMLElement>;
|
||||||
|
}
|
||||||
|
interface ddeHTMLElementTagNameMap {
|
||||||
|
"a": ddeHTMLAnchorElement;
|
||||||
|
"area": ddeHTMLAreaElement;
|
||||||
|
"audio": ddeHTMLAudioElement;
|
||||||
|
"base": ddeHTMLBaseElement;
|
||||||
|
"blockquote": ddeHTMLQuoteElement;
|
||||||
|
"body": ddeHTMLBodyElement;
|
||||||
|
"br": ddeHTMLBRElement;
|
||||||
|
"button": ddeHTMLButtonElement;
|
||||||
|
"canvas": ddeHTMLCanvasElement;
|
||||||
|
"caption": ddeHTMLTableCaptionElement;
|
||||||
|
"col": ddeHTMLTableColElement;
|
||||||
|
"colgroup": ddeHTMLTableColElement;
|
||||||
|
"data": ddeHTMLDataElement;
|
||||||
|
"datalist": ddeHTMLDataListElement;
|
||||||
|
"del": ddeHTMLModElement;
|
||||||
|
"details": ddeHTMLDetailsElement;
|
||||||
|
"dialog": ddeHTMLDialogElement;
|
||||||
|
"div": ddeHTMLDivElement;
|
||||||
|
"dl": ddeHTMLDListElement;
|
||||||
|
"embed": ddeHTMLEmbedElement;
|
||||||
|
"fieldset": ddeHTMLFieldSetElement;
|
||||||
|
"form": ddeHTMLFormElement;
|
||||||
|
"h1": ddeHTMLHeadingElement;
|
||||||
|
"h2": ddeHTMLHeadingElement;
|
||||||
|
"h3": ddeHTMLHeadingElement;
|
||||||
|
"h4": ddeHTMLHeadingElement;
|
||||||
|
"h5": ddeHTMLHeadingElement;
|
||||||
|
"h6": ddeHTMLHeadingElement;
|
||||||
|
"head": ddeHTMLHeadElement;
|
||||||
|
"hr": ddeHTMLHRElement;
|
||||||
|
"html": ddeHTMLHtmlElement;
|
||||||
|
"iframe": ddeHTMLIFrameElement;
|
||||||
|
"img": ddeHTMLImageElement;
|
||||||
|
"input": ddeHTMLInputElement;
|
||||||
|
"ins": ddeHTMLModElement;
|
||||||
|
"label": ddeHTMLLabelElement;
|
||||||
|
"legend": ddeHTMLLegendElement;
|
||||||
|
"li": ddeHTMLLIElement;
|
||||||
|
"link": ddeHTMLLinkElement;
|
||||||
|
"map": ddeHTMLMapElement;
|
||||||
|
"menu": ddeHTMLMenuElement;
|
||||||
|
"meta": ddeHTMLMetaElement;
|
||||||
|
"meter": ddeHTMLMeterElement;
|
||||||
|
"object": ddeHTMLObjectElement;
|
||||||
|
"ol": ddeHTMLOListElement;
|
||||||
|
"optgroup": ddeHTMLOptGroupElement;
|
||||||
|
"option": ddeHTMLOptionElement;
|
||||||
|
"output": ddeHTMLOutputElement;
|
||||||
|
"p": ddeHTMLParagraphElement;
|
||||||
|
"picture": ddeHTMLPictureElement;
|
||||||
|
"pre": ddeHTMLPreElement;
|
||||||
|
"progress": ddeHTMLProgressElement;
|
||||||
|
"q": ddeHTMLQuoteElement;
|
||||||
|
"script": ddeHTMLScriptElement;
|
||||||
|
"select": ddeHTMLSelectElement;
|
||||||
|
"slot": ddeHTMLSlotElement;
|
||||||
|
"source": ddeHTMLSourceElement;
|
||||||
|
"span": ddeHTMLSpanElement;
|
||||||
|
"style": ddeHTMLStyleElement;
|
||||||
|
"table": ddeHTMLTableElement;
|
||||||
|
"tbody": ddeHTMLTableSectionElement;
|
||||||
|
"td": ddeHTMLTableCellElement;
|
||||||
|
"template": ddeHTMLTemplateElement;
|
||||||
|
"textarea": ddeHTMLTextAreaElement;
|
||||||
|
"tfoot": ddeHTMLTableSectionElement;
|
||||||
|
"th": ddeHTMLTableCellElement;
|
||||||
|
"thead": ddeHTMLTableSectionElement;
|
||||||
|
"time": ddeHTMLTimeElement;
|
||||||
|
"title": ddeHTMLTitleElement;
|
||||||
|
"tr": ddeHTMLTableRowElement;
|
||||||
|
"track": ddeHTMLTrackElement;
|
||||||
|
"ul": ddeHTMLUListElement;
|
||||||
|
"video": ddeHTMLVideoElement;
|
||||||
|
}
|
||||||
|
interface ddeSVGElementTagNameMap {
|
||||||
|
"a": ddeSVGAElement;
|
||||||
|
"animate": ddeSVGAnimateElement;
|
||||||
|
"animateMotion": ddeSVGAnimateMotionElement;
|
||||||
|
"animateTransform": ddeSVGAnimateTransformElement;
|
||||||
|
"circle": ddeSVGCircleElement;
|
||||||
|
"clipPath": ddeSVGClipPathElement;
|
||||||
|
"defs": ddeSVGDefsElement;
|
||||||
|
"desc": ddeSVGDescElement;
|
||||||
|
"ellipse": ddeSVGEllipseElement;
|
||||||
|
"feBlend": ddeSVGFEBlendElement;
|
||||||
|
"feColorMatrix": ddeSVGFEColorMatrixElement;
|
||||||
|
"feComponentTransfer": ddeSVGFEComponentTransferElement;
|
||||||
|
"feComposite": ddeSVGFECompositeElement;
|
||||||
|
"feConvolveMatrix": ddeSVGFEConvolveMatrixElement;
|
||||||
|
"feDiffuseLighting": ddeSVGFEDiffuseLightingElement;
|
||||||
|
"feDisplacementMap": ddeSVGFEDisplacementMapElement;
|
||||||
|
"feDistantLight": ddeSVGFEDistantLightElement;
|
||||||
|
"feDropShadow": ddeSVGFEDropShadowElement;
|
||||||
|
"feFlood": ddeSVGFEFloodElement;
|
||||||
|
"feFuncA": ddeSVGFEFuncAElement;
|
||||||
|
"feFuncB": ddeSVGFEFuncBElement;
|
||||||
|
"feFuncG": ddeSVGFEFuncGElement;
|
||||||
|
"feFuncR": ddeSVGFEFuncRElement;
|
||||||
|
"feGaussianBlur": ddeSVGFEGaussianBlurElement;
|
||||||
|
"feImage": ddeSVGFEImageElement;
|
||||||
|
"feMerge": ddeSVGFEMergeElement;
|
||||||
|
"feMergeNode": ddeSVGFEMergeNodeElement;
|
||||||
|
"feMorphology": ddeSVGFEMorphologyElement;
|
||||||
|
"feOffset": ddeSVGFEOffsetElement;
|
||||||
|
"fePointLight": ddeSVGFEPointLightElement;
|
||||||
|
"feSpecularLighting": ddeSVGFESpecularLightingElement;
|
||||||
|
"feSpotLight": ddeSVGFESpotLightElement;
|
||||||
|
"feTile": ddeSVGFETileElement;
|
||||||
|
"feTurbulence": ddeSVGFETurbulenceElement;
|
||||||
|
"filter": ddeSVGFilterElement;
|
||||||
|
"foreignObject": ddeSVGForeignObjectElement;
|
||||||
|
"g": ddeSVGGElement;
|
||||||
|
"image": ddeSVGImageElement;
|
||||||
|
"line": ddeSVGLineElement;
|
||||||
|
"linearGradient": ddeSVGLinearGradientElement;
|
||||||
|
"marker": ddeSVGMarkerElement;
|
||||||
|
"mask": ddeSVGMaskElement;
|
||||||
|
"metadata": ddeSVGMetadataElement;
|
||||||
|
"mpath": ddeSVGMPathElement;
|
||||||
|
"path": ddeSVGPathElement;
|
||||||
|
"pattern": ddeSVGPatternElement;
|
||||||
|
"polygon": ddeSVGPolygonElement;
|
||||||
|
"polyline": ddeSVGPolylineElement;
|
||||||
|
"radialGradient": ddeSVGRadialGradientElement;
|
||||||
|
"rect": ddeSVGRectElement;
|
||||||
|
"script": ddeSVGScriptElement;
|
||||||
|
"set": ddeSVGSetElement;
|
||||||
|
"stop": ddeSVGStopElement;
|
||||||
|
"style": ddeSVGStyleElement;
|
||||||
|
"svg": ddeSVGSVGElement;
|
||||||
|
"switch": ddeSVGSwitchElement;
|
||||||
|
"symbol": ddeSVGSymbolElement;
|
||||||
|
"text": ddeSVGTextElement;
|
||||||
|
"textPath": ddeSVGTextPathElement;
|
||||||
|
"title": ddeSVGTitleElement;
|
||||||
|
"tspan": ddeSVGTSpanElement;
|
||||||
|
"use": ddeSVGUseElement;
|
||||||
|
"view": ddeSVGViewElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// editorconfig-checker-disable
|
||||||
|
export interface ddeHTMLAnchorElement extends HTMLAnchorElement {
|
||||||
|
append: ddeAppend<ddeHTMLAnchorElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLAreaElement extends HTMLAreaElement {
|
||||||
|
append: ddeAppend<ddeHTMLAreaElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLAudioElement extends HTMLAudioElement {
|
||||||
|
append: ddeAppend<ddeHTMLAudioElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLBaseElement extends HTMLBaseElement {
|
||||||
|
append: ddeAppend<ddeHTMLBaseElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLQuoteElement extends HTMLQuoteElement {
|
||||||
|
append: ddeAppend<ddeHTMLQuoteElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLBodyElement extends HTMLBodyElement {
|
||||||
|
append: ddeAppend<ddeHTMLBodyElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLBRElement extends HTMLBRElement {
|
||||||
|
append: ddeAppend<ddeHTMLBRElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLButtonElement extends HTMLButtonElement {
|
||||||
|
append: ddeAppend<ddeHTMLButtonElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLCanvasElement extends HTMLCanvasElement {
|
||||||
|
append: ddeAppend<ddeHTMLCanvasElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableCaptionElement extends HTMLTableCaptionElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableCaptionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableColElement extends HTMLTableColElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableColElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableColElement extends HTMLTableColElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableColElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDataElement extends HTMLDataElement {
|
||||||
|
append: ddeAppend<ddeHTMLDataElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDataListElement extends HTMLDataListElement {
|
||||||
|
append: ddeAppend<ddeHTMLDataListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLModElement extends HTMLModElement {
|
||||||
|
append: ddeAppend<ddeHTMLModElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDetailsElement extends HTMLDetailsElement {
|
||||||
|
append: ddeAppend<ddeHTMLDetailsElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDialogElement extends HTMLDialogElement {
|
||||||
|
append: ddeAppend<ddeHTMLDialogElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDivElement extends HTMLDivElement {
|
||||||
|
append: ddeAppend<ddeHTMLDivElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDListElement extends HTMLDListElement {
|
||||||
|
append: ddeAppend<ddeHTMLDListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLEmbedElement extends HTMLEmbedElement {
|
||||||
|
append: ddeAppend<ddeHTMLEmbedElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLFieldSetElement extends HTMLFieldSetElement {
|
||||||
|
append: ddeAppend<ddeHTMLFieldSetElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLFormElement extends HTMLFormElement {
|
||||||
|
append: ddeAppend<ddeHTMLFormElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHeadingElement extends HTMLHeadingElement {
|
||||||
|
append: ddeAppend<ddeHTMLHeadingElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHeadElement extends HTMLHeadElement {
|
||||||
|
append: ddeAppend<ddeHTMLHeadElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHRElement extends HTMLHRElement {
|
||||||
|
append: ddeAppend<ddeHTMLHRElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHtmlElement extends HTMLHtmlElement {
|
||||||
|
append: ddeAppend<ddeHTMLHtmlElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLIFrameElement extends HTMLIFrameElement {
|
||||||
|
append: ddeAppend<ddeHTMLIFrameElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLImageElement extends HTMLImageElement {
|
||||||
|
append: ddeAppend<ddeHTMLImageElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLInputElement extends HTMLInputElement {
|
||||||
|
append: ddeAppend<ddeHTMLInputElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLabelElement extends HTMLLabelElement {
|
||||||
|
append: ddeAppend<ddeHTMLLabelElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLegendElement extends HTMLLegendElement {
|
||||||
|
append: ddeAppend<ddeHTMLLegendElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLIElement extends HTMLLIElement {
|
||||||
|
append: ddeAppend<ddeHTMLLIElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLinkElement extends HTMLLinkElement {
|
||||||
|
append: ddeAppend<ddeHTMLLinkElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMapElement extends HTMLMapElement {
|
||||||
|
append: ddeAppend<ddeHTMLMapElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMenuElement extends HTMLMenuElement {
|
||||||
|
append: ddeAppend<ddeHTMLMenuElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMetaElement extends HTMLMetaElement {
|
||||||
|
append: ddeAppend<ddeHTMLMetaElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMeterElement extends HTMLMeterElement {
|
||||||
|
append: ddeAppend<ddeHTMLMeterElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLObjectElement extends HTMLObjectElement {
|
||||||
|
append: ddeAppend<ddeHTMLObjectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOListElement extends HTMLOListElement {
|
||||||
|
append: ddeAppend<ddeHTMLOListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOptGroupElement extends HTMLOptGroupElement {
|
||||||
|
append: ddeAppend<ddeHTMLOptGroupElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOptionElement extends HTMLOptionElement {
|
||||||
|
append: ddeAppend<ddeHTMLOptionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOutputElement extends HTMLOutputElement {
|
||||||
|
append: ddeAppend<ddeHTMLOutputElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLParagraphElement extends HTMLParagraphElement {
|
||||||
|
append: ddeAppend<ddeHTMLParagraphElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLPictureElement extends HTMLPictureElement {
|
||||||
|
append: ddeAppend<ddeHTMLPictureElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLPreElement extends HTMLPreElement {
|
||||||
|
append: ddeAppend<ddeHTMLPreElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLProgressElement extends HTMLProgressElement {
|
||||||
|
append: ddeAppend<ddeHTMLProgressElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLScriptElement extends HTMLScriptElement {
|
||||||
|
append: ddeAppend<ddeHTMLScriptElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSelectElement extends HTMLSelectElement {
|
||||||
|
append: ddeAppend<ddeHTMLSelectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSlotElement extends HTMLSlotElement {
|
||||||
|
append: ddeAppend<ddeHTMLSlotElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSourceElement extends HTMLSourceElement {
|
||||||
|
append: ddeAppend<ddeHTMLSourceElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSpanElement extends HTMLSpanElement {
|
||||||
|
append: ddeAppend<ddeHTMLSpanElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLStyleElement extends HTMLStyleElement {
|
||||||
|
append: ddeAppend<ddeHTMLStyleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableElement extends HTMLTableElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableSectionElement extends HTMLTableSectionElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableSectionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableCellElement extends HTMLTableCellElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableCellElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTemplateElement extends HTMLTemplateElement {
|
||||||
|
append: ddeAppend<ddeHTMLTemplateElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTextAreaElement extends HTMLTextAreaElement {
|
||||||
|
append: ddeAppend<ddeHTMLTextAreaElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableCellElement extends HTMLTableCellElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableCellElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTimeElement extends HTMLTimeElement {
|
||||||
|
append: ddeAppend<ddeHTMLTimeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTitleElement extends HTMLTitleElement {
|
||||||
|
append: ddeAppend<ddeHTMLTitleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableRowElement extends HTMLTableRowElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableRowElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTrackElement extends HTMLTrackElement {
|
||||||
|
append: ddeAppend<ddeHTMLTrackElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLUListElement extends HTMLUListElement {
|
||||||
|
append: ddeAppend<ddeHTMLUListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLVideoElement extends HTMLVideoElement {
|
||||||
|
append: ddeAppend<ddeHTMLVideoElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAElement extends SVGAElement {
|
||||||
|
append: ddeAppend<ddeSVGAElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAnimateElement extends SVGAnimateElement {
|
||||||
|
append: ddeAppend<ddeSVGAnimateElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAnimateMotionElement extends SVGAnimateMotionElement {
|
||||||
|
append: ddeAppend<ddeSVGAnimateMotionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAnimateTransformElement extends SVGAnimateTransformElement {
|
||||||
|
append: ddeAppend<ddeSVGAnimateTransformElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGCircleElement extends SVGCircleElement {
|
||||||
|
append: ddeAppend<ddeSVGCircleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGClipPathElement extends SVGClipPathElement {
|
||||||
|
append: ddeAppend<ddeSVGClipPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGDefsElement extends SVGDefsElement {
|
||||||
|
append: ddeAppend<ddeSVGDefsElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGDescElement extends SVGDescElement {
|
||||||
|
append: ddeAppend<ddeSVGDescElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGEllipseElement extends SVGEllipseElement {
|
||||||
|
append: ddeAppend<ddeSVGEllipseElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEBlendElement extends SVGFEBlendElement {
|
||||||
|
append: ddeAppend<ddeSVGFEBlendElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEColorMatrixElement extends SVGFEColorMatrixElement {
|
||||||
|
append: ddeAppend<ddeSVGFEColorMatrixElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEComponentTransferElement extends SVGFEComponentTransferElement {
|
||||||
|
append: ddeAppend<ddeSVGFEComponentTransferElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFECompositeElement extends SVGFECompositeElement {
|
||||||
|
append: ddeAppend<ddeSVGFECompositeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEConvolveMatrixElement extends SVGFEConvolveMatrixElement {
|
||||||
|
append: ddeAppend<ddeSVGFEConvolveMatrixElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDiffuseLightingElement extends SVGFEDiffuseLightingElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDiffuseLightingElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDisplacementMapElement extends SVGFEDisplacementMapElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDisplacementMapElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDistantLightElement extends SVGFEDistantLightElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDistantLightElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDropShadowElement extends SVGFEDropShadowElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDropShadowElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFloodElement extends SVGFEFloodElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFloodElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncAElement extends SVGFEFuncAElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncAElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncBElement extends SVGFEFuncBElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncBElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncGElement extends SVGFEFuncGElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncGElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncRElement extends SVGFEFuncRElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncRElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEGaussianBlurElement extends SVGFEGaussianBlurElement {
|
||||||
|
append: ddeAppend<ddeSVGFEGaussianBlurElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEImageElement extends SVGFEImageElement {
|
||||||
|
append: ddeAppend<ddeSVGFEImageElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEMergeElement extends SVGFEMergeElement {
|
||||||
|
append: ddeAppend<ddeSVGFEMergeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEMergeNodeElement extends SVGFEMergeNodeElement {
|
||||||
|
append: ddeAppend<ddeSVGFEMergeNodeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEMorphologyElement extends SVGFEMorphologyElement {
|
||||||
|
append: ddeAppend<ddeSVGFEMorphologyElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEOffsetElement extends SVGFEOffsetElement {
|
||||||
|
append: ddeAppend<ddeSVGFEOffsetElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEPointLightElement extends SVGFEPointLightElement {
|
||||||
|
append: ddeAppend<ddeSVGFEPointLightElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFESpecularLightingElement extends SVGFESpecularLightingElement {
|
||||||
|
append: ddeAppend<ddeSVGFESpecularLightingElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFESpotLightElement extends SVGFESpotLightElement {
|
||||||
|
append: ddeAppend<ddeSVGFESpotLightElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFETileElement extends SVGFETileElement {
|
||||||
|
append: ddeAppend<ddeSVGFETileElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFETurbulenceElement extends SVGFETurbulenceElement {
|
||||||
|
append: ddeAppend<ddeSVGFETurbulenceElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFilterElement extends SVGFilterElement {
|
||||||
|
append: ddeAppend<ddeSVGFilterElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGForeignObjectElement extends SVGForeignObjectElement {
|
||||||
|
append: ddeAppend<ddeSVGForeignObjectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGGElement extends SVGGElement {
|
||||||
|
append: ddeAppend<ddeSVGGElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGImageElement extends SVGImageElement {
|
||||||
|
append: ddeAppend<ddeSVGImageElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGLineElement extends SVGLineElement {
|
||||||
|
append: ddeAppend<ddeSVGLineElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGLinearGradientElement extends SVGLinearGradientElement {
|
||||||
|
append: ddeAppend<ddeSVGLinearGradientElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMarkerElement extends SVGMarkerElement {
|
||||||
|
append: ddeAppend<ddeSVGMarkerElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMaskElement extends SVGMaskElement {
|
||||||
|
append: ddeAppend<ddeSVGMaskElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMetadataElement extends SVGMetadataElement {
|
||||||
|
append: ddeAppend<ddeSVGMetadataElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMPathElement extends SVGMPathElement {
|
||||||
|
append: ddeAppend<ddeSVGMPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPathElement extends SVGPathElement {
|
||||||
|
append: ddeAppend<ddeSVGPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPatternElement extends SVGPatternElement {
|
||||||
|
append: ddeAppend<ddeSVGPatternElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPolygonElement extends SVGPolygonElement {
|
||||||
|
append: ddeAppend<ddeSVGPolygonElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPolylineElement extends SVGPolylineElement {
|
||||||
|
append: ddeAppend<ddeSVGPolylineElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGRadialGradientElement extends SVGRadialGradientElement {
|
||||||
|
append: ddeAppend<ddeSVGRadialGradientElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGRectElement extends SVGRectElement {
|
||||||
|
append: ddeAppend<ddeSVGRectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGScriptElement extends SVGScriptElement {
|
||||||
|
append: ddeAppend<ddeSVGScriptElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSetElement extends SVGSetElement {
|
||||||
|
append: ddeAppend<ddeSVGSetElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGStopElement extends SVGStopElement {
|
||||||
|
append: ddeAppend<ddeSVGStopElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGStyleElement extends SVGStyleElement {
|
||||||
|
append: ddeAppend<ddeSVGStyleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSVGElement extends SVGSVGElement {
|
||||||
|
append: ddeAppend<ddeSVGSVGElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSwitchElement extends SVGSwitchElement {
|
||||||
|
append: ddeAppend<ddeSVGSwitchElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSymbolElement extends SVGSymbolElement {
|
||||||
|
append: ddeAppend<ddeSVGSymbolElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTextElement extends SVGTextElement {
|
||||||
|
append: ddeAppend<ddeSVGTextElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTextPathElement extends SVGTextPathElement {
|
||||||
|
append: ddeAppend<ddeSVGTextPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTitleElement extends SVGTitleElement {
|
||||||
|
append: ddeAppend<ddeSVGTitleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTSpanElement extends SVGTSpanElement {
|
||||||
|
append: ddeAppend<ddeSVGTSpanElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGUseElement extends SVGUseElement {
|
||||||
|
append: ddeAppend<ddeSVGUseElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGViewElement extends SVGViewElement {
|
||||||
|
append: ddeAppend<ddeSVGViewElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
dispatchEvent$1 as dispatchEvent,
|
||||||
|
el as createElement,
|
||||||
|
elNS as createElementNS,
|
||||||
|
};
|
||||||
|
|
||||||
|
export as namespace iife;
|
||||||
|
|
||||||
|
export {};
|
1029
dist/iife-with-signals.js
vendored
Normal file
1029
dist/iife-with-signals.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
862
dist/iife-with-signals.min.d.ts
vendored
Normal file
862
dist/iife-with-signals.min.d.ts
vendored
Normal file
@ -0,0 +1,862 @@
|
|||||||
|
// Generated by dts-bundle-generator v9.5.1
|
||||||
|
|
||||||
|
export interface Signal<V, A> {
|
||||||
|
/** The current value of the signal */
|
||||||
|
get(): V;
|
||||||
|
/** Set new value of the signal */
|
||||||
|
set(value: V, force?: boolean): V;
|
||||||
|
toJSON(): V;
|
||||||
|
valueOf(): V;
|
||||||
|
}
|
||||||
|
export type Action<V> = (this: {
|
||||||
|
value: V;
|
||||||
|
stopPropagation(): void;
|
||||||
|
}, ...a: any[]) => typeof signal._ | void;
|
||||||
|
//type SymbolSignal= Symbol;
|
||||||
|
export type SymbolOnclear = symbol;
|
||||||
|
export type Actions<V> = Record<string | SymbolOnclear, Action<V>>;
|
||||||
|
export type OnListenerOptions = Pick<AddEventListenerOptions, "signal"> & {
|
||||||
|
first_time?: boolean;
|
||||||
|
};
|
||||||
|
export type SElement = Node | Element | DocumentFragment | ddeHTMLElement | ddeSVGElement | ddeDocumentFragment;
|
||||||
|
export interface signal {
|
||||||
|
_: Symbol;
|
||||||
|
/**
|
||||||
|
* Computations signal. This creates a signal which is computed from other signals.
|
||||||
|
* */
|
||||||
|
<V extends () => any>(computation: V): Signal<ReturnType<V>, {}>;
|
||||||
|
/**
|
||||||
|
* Simple example:
|
||||||
|
* ```js
|
||||||
|
* const hello= S("Hello Signal");
|
||||||
|
* ```
|
||||||
|
* …simple todo signal:
|
||||||
|
* ```js
|
||||||
|
* const todos= S([], {
|
||||||
|
* add(v){ this.value.push(S(v)); },
|
||||||
|
* remove(i){ this.value.splice(i, 1); },
|
||||||
|
* [S.symbols.onclear](){ S.clear(...this.value); },
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* …computed signal:
|
||||||
|
* ```js
|
||||||
|
* const name= S("Jan");
|
||||||
|
* const surname= S("Andrle");
|
||||||
|
* const fullname= S(()=> name.get()+" "+surname.get());
|
||||||
|
* ```
|
||||||
|
* @param value Initial signal value. Or function computing value from other signals.
|
||||||
|
* @param actions Use to define actions on the signal. Such as add item to the array.
|
||||||
|
* There is also a reserved function `S.symbol.onclear` which is called when the signal is cleared
|
||||||
|
* by `S.clear`.
|
||||||
|
* */
|
||||||
|
<V, A extends Actions<V>>(value: V, actions?: A): Signal<V, A>;
|
||||||
|
action<S extends Signal<any, Actions<any>>, A extends (S extends Signal<any, infer A> ? A : never), N extends keyof A>(signal: S, name: N, ...params: A[N] extends (...args: infer P) => any ? P : never): void;
|
||||||
|
clear(...signals: Signal<any, any>[]): void;
|
||||||
|
on<T>(signal: Signal<T, any>, onchange: (a: T) => void, options?: OnListenerOptions): void;
|
||||||
|
symbols: {
|
||||||
|
//signal: SymbolSignal;
|
||||||
|
onclear: SymbolOnclear;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Reactive element, which is rendered based on the given signal.
|
||||||
|
* ```js
|
||||||
|
* S.el(signal, value=> value ? el("b", "True") : el("i", "False"));
|
||||||
|
* S.el(listS, list=> list.map(li=> el("li", li)));
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
el<S extends any>(signal: Signal<S, any>, el: (v: S) => SElement | SElement[]): DocumentFragment;
|
||||||
|
observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>;
|
||||||
|
}
|
||||||
|
export const signal: signal;
|
||||||
|
export const S: signal;
|
||||||
|
declare global {
|
||||||
|
type ddeSignal<T, A = {}> = Signal<T, A>;
|
||||||
|
type ddeAction<V> = Action<V>;
|
||||||
|
type ddeActions<V> = Actions<V>;
|
||||||
|
}
|
||||||
|
export type CustomElementTagNameMap = {
|
||||||
|
"#text": Text;
|
||||||
|
"#comment": Comment;
|
||||||
|
};
|
||||||
|
export type SupportedElement = HTMLElementTagNameMap[keyof HTMLElementTagNameMap] | SVGElementTagNameMap[keyof SVGElementTagNameMap] | MathMLElementTagNameMap[keyof MathMLElementTagNameMap] | CustomElementTagNameMap[keyof CustomElementTagNameMap];
|
||||||
|
declare global {
|
||||||
|
type ddeComponentAttributes = Record<any, any> | undefined;
|
||||||
|
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El, ...rest: any) => any;
|
||||||
|
type ddeString = string | Signal<string, {}>;
|
||||||
|
type ddeStringable = ddeString | number | Signal<number, {}>;
|
||||||
|
}
|
||||||
|
export type Host<EL extends SupportedElement> = (...addons: ddeElementAddon<EL>[]) => EL;
|
||||||
|
export type PascalCase = `${Capitalize<string>}${string}`;
|
||||||
|
export type AttrsModified = {
|
||||||
|
/**
|
||||||
|
* Use string like in HTML (internally uses `*.setAttribute("style", *)`), or object representation (like DOM API).
|
||||||
|
*/
|
||||||
|
style: Partial<CSSStyleDeclaration> | ddeString | Partial<{
|
||||||
|
[K in keyof CSSStyleDeclaration]: Signal<CSSStyleDeclaration[K], {}>;
|
||||||
|
}>;
|
||||||
|
/**
|
||||||
|
* Provide option to add/remove/toggle CSS clasess (index of object) using 1/0/-1.
|
||||||
|
* In fact `el.classList.toggle(class_name)` for `-1` and `el.classList.toggle(class_name, Boolean(...))`
|
||||||
|
* for others.
|
||||||
|
*/
|
||||||
|
classList: Record<string, -1 | 0 | 1 | boolean | Signal<-1 | 0 | 1 | boolean, {}>>;
|
||||||
|
/**
|
||||||
|
* Used by the dataset HTML attribute to represent data for custom attributes added to elements.
|
||||||
|
* Values are converted to string (see {@link DOMStringMap}).
|
||||||
|
*
|
||||||
|
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMStringMap)
|
||||||
|
* */
|
||||||
|
dataset: Record<string, ddeStringable>;
|
||||||
|
/**
|
||||||
|
* Sets `aria-*` simiraly to `dataset`
|
||||||
|
* */
|
||||||
|
ariaset: Record<string, ddeString>;
|
||||||
|
} & Record<`=${string}` | `data${PascalCase}` | `aria${PascalCase}`, ddeString> & Record<`.${string}`, any>;
|
||||||
|
export type _fromElsInterfaces<EL extends SupportedElement> = Omit<EL, keyof AttrsModified>;
|
||||||
|
export type IsReadonly<T, K extends keyof T> = T extends {
|
||||||
|
readonly [P in K]: T[K];
|
||||||
|
} ? true : false;
|
||||||
|
/**
|
||||||
|
* Just element attributtes
|
||||||
|
*
|
||||||
|
* In most cases, you can use native propertie such as
|
||||||
|
* [MDN WEB/API/Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) and so on
|
||||||
|
* (e.g. [`Text`](https://developer.mozilla.org/en-US/docs/Web/API/Text)).
|
||||||
|
*
|
||||||
|
* There is added support for `data[A-Z].*`/`aria[A-Z].*` to be converted to the kebab-case alternatives.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export type ElementAttributes<T extends SupportedElement> = Partial<{
|
||||||
|
[K in keyof _fromElsInterfaces<T>]: _fromElsInterfaces<T>[K] extends ((...p: any[]) => any) ? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>) => Signal<ReturnType<_fromElsInterfaces<T>[K]>, {}>) : (IsReadonly<_fromElsInterfaces<T>, K> extends false ? _fromElsInterfaces<T>[K] | Signal<_fromElsInterfaces<T>[K], {}> : ddeStringable);
|
||||||
|
} & AttrsModified> & Record<string, any>;
|
||||||
|
export function classListDeclarative<El extends SupportedElement>(element: El, classList: AttrsModified["classList"]): El;
|
||||||
|
export function assign<El extends SupportedElement>(element: El, ...attrs_array: ElementAttributes<El>[]): El;
|
||||||
|
export function assignAttribute<El extends SupportedElement, ATT extends keyof ElementAttributes<El>>(element: El, attr: ATT, value: ElementAttributes<El>[ATT]): ElementAttributes<El>[ATT];
|
||||||
|
export type ExtendedHTMLElementTagNameMap = HTMLElementTagNameMap & CustomElementTagNameMap;
|
||||||
|
export namespace el {
|
||||||
|
/**
|
||||||
|
* Creates a marker comment for elements
|
||||||
|
*
|
||||||
|
* @param attrs - Marker attributes
|
||||||
|
* @param [is_open=false] - Whether the marker is open-ended
|
||||||
|
* @returns Comment node marker
|
||||||
|
*/
|
||||||
|
export function mark(attrs: {
|
||||||
|
type: "component" | "reactive" | "later";
|
||||||
|
name?: string;
|
||||||
|
host?: "this" | "parentElement";
|
||||||
|
}, is_open?: boolean): Comment;
|
||||||
|
}
|
||||||
|
export function chainableAppend<EL extends SupportedElement>(el: EL): EL | ddeHTMLElement;
|
||||||
|
export function el<A extends ddeComponentAttributes, EL extends SupportedElement | ddeDocumentFragment>(component: (attr: A, ...rest: any[]) => EL, attrs?: NoInfer<A>, ...addons: ddeElementAddon<EL>[]): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? EL : (EL extends ddeDocumentFragment ? EL : ddeHTMLElement);
|
||||||
|
export function el<A extends {
|
||||||
|
textContent: ddeStringable;
|
||||||
|
}, EL extends SupportedElement | ddeDocumentFragment>(component: (attr: A, ...rest: any[]) => EL, attrs?: NoInfer<A>["textContent"], ...addons: ddeElementAddon<EL>[]): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? EL : (EL extends ddeDocumentFragment ? EL : ddeHTMLElement);
|
||||||
|
export function el<TAG extends keyof ExtendedHTMLElementTagNameMap>(tag_name: TAG, attrs?: ElementAttributes<ExtendedHTMLElementTagNameMap[NoInfer<TAG>]> | ddeStringable, ...addons: ddeElementAddon<ExtendedHTMLElementTagNameMap[NoInfer<TAG>]>[]): TAG extends keyof ddeHTMLElementTagNameMap ? ddeHTMLElementTagNameMap[TAG] : ddeHTMLElement;
|
||||||
|
export function el(tag_name?: "<>"): ddeDocumentFragment;
|
||||||
|
export function el(tag_name: string, attrs?: ElementAttributes<HTMLElement> | ddeStringable, ...addons: ddeElementAddon<HTMLElement>[]): ddeHTMLElement;
|
||||||
|
export function elNS(namespace: "http://www.w3.org/2000/svg"): <TAG extends keyof SVGElementTagNameMap & string, EL extends (TAG extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[TAG] : SVGElement)>(tag_name: TAG, attrs?: ElementAttributes<NoInfer<EL>> | ddeStringable, ...addons: ddeElementAddon<NoInfer<EL>>[]) => TAG extends keyof ddeSVGElementTagNameMap ? ddeSVGElementTagNameMap[TAG] : ddeSVGElement;
|
||||||
|
export function elNS(namespace: "http://www.w3.org/1998/Math/MathML"): <TAG extends keyof MathMLElementTagNameMap & string, EL extends (TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement)>(tag_name: TAG, attrs?: ddeStringable | Partial<{
|
||||||
|
[key in keyof EL]: EL[key] | Signal<EL[key], {}> | string | number | boolean;
|
||||||
|
}>, ...addons: ddeElementAddon<NoInfer<EL>>[]) => ddeMathMLElement;
|
||||||
|
export function elNS(namespace: string): (tag_name: string, attrs?: string | ddeStringable | Record<string, any>, ...addons: ddeElementAddon<SupportedElement>[]) => SupportedElement;
|
||||||
|
/** Simulate slots for ddeComponents */
|
||||||
|
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(root: EL): EL;
|
||||||
|
/**
|
||||||
|
* Simulate slots in Custom Elements without using `shadowRoot`.
|
||||||
|
* @param el Custom Element root element
|
||||||
|
* @param body Body of the custom element
|
||||||
|
* */
|
||||||
|
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(el: HTMLElement, body: EL): EL;
|
||||||
|
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, host: Host<SupportedElement>): (data?: any) => void;
|
||||||
|
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?: EventInit): (element: SupportedElement, data?: any) => void;
|
||||||
|
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
|
||||||
|
export interface On {
|
||||||
|
/** Listens to the DOM event. See {@link Document.addEventListener} */
|
||||||
|
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & {
|
||||||
|
target: EL;
|
||||||
|
}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
|
||||||
|
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
|
||||||
|
/** 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>;
|
||||||
|
/** 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>;
|
||||||
|
/**
|
||||||
|
* Fires after the next tick of the Javascript event loop.
|
||||||
|
* This is handy for example to apply some property depending on the element content:
|
||||||
|
* ```js
|
||||||
|
* const selected= "Z";
|
||||||
|
* //...
|
||||||
|
* return el("form").append(
|
||||||
|
* el("select", null, on.defer(e=> e.value=selected)).append(
|
||||||
|
* el("option", { value: "A", textContent: "A" }),
|
||||||
|
* //...
|
||||||
|
* el("option", { value: "Z", textContent: "Z" }),
|
||||||
|
* ),
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>;
|
||||||
|
}
|
||||||
|
export const on: On;
|
||||||
|
export type Scope = {
|
||||||
|
scope: Node | Function | Object;
|
||||||
|
host: Host<SupportedElement>;
|
||||||
|
custom_element: false | HTMLElement;
|
||||||
|
prevent: boolean;
|
||||||
|
};
|
||||||
|
/** Current scope created last time the `el(Function)` was invoke. (Or {@link scope.push}) */
|
||||||
|
export const scope: {
|
||||||
|
current: Scope;
|
||||||
|
/** Stops all automatizations. E. g. signals used as attributes in current scope
|
||||||
|
* registers removing these listeners (and clean signal if no other listeners are detected)
|
||||||
|
* on `disconnected` event. */
|
||||||
|
preventDefault<T extends boolean>(prevent: T): T;
|
||||||
|
/**
|
||||||
|
* This represents reference to the current host element — `scope.host()`.
|
||||||
|
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
||||||
|
* — `scope.host(on.connected(console.log))`.
|
||||||
|
* */
|
||||||
|
host: Host<SupportedElement>;
|
||||||
|
/**
|
||||||
|
* Creates/gets an AbortController that triggers when the element disconnects
|
||||||
|
* */
|
||||||
|
signal: AbortSignal;
|
||||||
|
state: Scope[];
|
||||||
|
/** Adds new child scope. All attributes are inherited by default. */
|
||||||
|
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>;
|
||||||
|
/** Adds root scope as a child of the current scope. */
|
||||||
|
pushRoot(): ReturnType<Array<Scope>["push"]>;
|
||||||
|
/** Removes last/current child scope. */
|
||||||
|
pop(): ReturnType<Array<Scope>["pop"]>;
|
||||||
|
};
|
||||||
|
export function customElementRender<EL extends HTMLElement, P extends any = Record<string, string | Signal<string, {}>>>(target: ShadowRoot | EL, render: (props: P) => SupportedElement | DocumentFragment, props?: P | ((el: EL) => P)): EL;
|
||||||
|
export function customElementWithDDE<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||||
|
export function lifecyclesToEvents<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||||
|
/**
|
||||||
|
* This is used primarly for server side rendering. To be sure that all async operations
|
||||||
|
* are finished before the page is sent to the client.
|
||||||
|
* ```
|
||||||
|
* // on component
|
||||||
|
* function component(){
|
||||||
|
* …
|
||||||
|
* queue(fetch(...).then(...));
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // building the page
|
||||||
|
* async function build(){
|
||||||
|
* const { component }= await import("./component.js");
|
||||||
|
* document.body.append(el(component));
|
||||||
|
* await queue();
|
||||||
|
* retutn document.body.innerHTML;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
export function queue(promise?: Promise<unknown>): Promise<unknown>;
|
||||||
|
/**
|
||||||
|
* Memoization utility for caching DOM elements to improve performance.
|
||||||
|
* Used to prevent unnecessary recreation of elements when rendering lists or complex components.
|
||||||
|
*
|
||||||
|
* @param key - Unique identifier for the element (usually an ID or unique value)
|
||||||
|
* @param generator - Function that creates the element
|
||||||
|
* @returns The cached element if the key exists, otherwise the result of the generator function
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* // Within S.el for list rendering
|
||||||
|
* S.el(itemsSignal, (items, memo) =>
|
||||||
|
* el("ul").append(
|
||||||
|
* ...items.map(item =>
|
||||||
|
* memo(item.id, () => el(ItemComponent, item))
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function memo<T>(key: string | number | object, generator: (key: any) => T): T;
|
||||||
|
/**
|
||||||
|
* Memo namespace containing utility functions for memoization.
|
||||||
|
*/
|
||||||
|
export namespace memo {
|
||||||
|
/**
|
||||||
|
* Checks if an object is a memo scope.
|
||||||
|
* @param obj - The object to check
|
||||||
|
* @returns True if the object is a memo scope
|
||||||
|
*/
|
||||||
|
export function isScope(obj: any): boolean;
|
||||||
|
/**
|
||||||
|
* Creates a memoized function with optional cleanup support.
|
||||||
|
*
|
||||||
|
* @param fun - The function to memoize
|
||||||
|
* @param options - Configuration options
|
||||||
|
* @param options.signal - AbortSignal for cleanup
|
||||||
|
* @param options.onlyLast - When true, only keeps the cache from the most recent call
|
||||||
|
* @returns A memoized version of the function with a .clear() method
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const renderItems = memo.scope(function(items) {
|
||||||
|
* return items.map(item =>
|
||||||
|
* memo(item.id, () => el("div", item.name))
|
||||||
|
* );
|
||||||
|
* }, {
|
||||||
|
* signal: controller.signal,
|
||||||
|
* onlyLast: true
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function scope<F extends Function>(fun: F, options?: {
|
||||||
|
signal?: AbortSignal;
|
||||||
|
onlyLast?: boolean;
|
||||||
|
}): F & {
|
||||||
|
clear: () => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/* TypeScript MEH */
|
||||||
|
declare global {
|
||||||
|
type ddeAppend<el> = (...nodes: (Node | string)[]) => el;
|
||||||
|
interface ddeDocumentFragment extends DocumentFragment {
|
||||||
|
append: ddeAppend<ddeDocumentFragment>;
|
||||||
|
}
|
||||||
|
interface ddeHTMLElement extends HTMLElement {
|
||||||
|
append: ddeAppend<ddeHTMLElement>;
|
||||||
|
}
|
||||||
|
interface ddeSVGElement extends SVGElement {
|
||||||
|
append: ddeAppend<ddeSVGElement>;
|
||||||
|
}
|
||||||
|
interface ddeMathMLElement extends MathMLElement {
|
||||||
|
append: ddeAppend<ddeMathMLElement>;
|
||||||
|
}
|
||||||
|
interface ddeHTMLElementTagNameMap {
|
||||||
|
"a": ddeHTMLAnchorElement;
|
||||||
|
"area": ddeHTMLAreaElement;
|
||||||
|
"audio": ddeHTMLAudioElement;
|
||||||
|
"base": ddeHTMLBaseElement;
|
||||||
|
"blockquote": ddeHTMLQuoteElement;
|
||||||
|
"body": ddeHTMLBodyElement;
|
||||||
|
"br": ddeHTMLBRElement;
|
||||||
|
"button": ddeHTMLButtonElement;
|
||||||
|
"canvas": ddeHTMLCanvasElement;
|
||||||
|
"caption": ddeHTMLTableCaptionElement;
|
||||||
|
"col": ddeHTMLTableColElement;
|
||||||
|
"colgroup": ddeHTMLTableColElement;
|
||||||
|
"data": ddeHTMLDataElement;
|
||||||
|
"datalist": ddeHTMLDataListElement;
|
||||||
|
"del": ddeHTMLModElement;
|
||||||
|
"details": ddeHTMLDetailsElement;
|
||||||
|
"dialog": ddeHTMLDialogElement;
|
||||||
|
"div": ddeHTMLDivElement;
|
||||||
|
"dl": ddeHTMLDListElement;
|
||||||
|
"embed": ddeHTMLEmbedElement;
|
||||||
|
"fieldset": ddeHTMLFieldSetElement;
|
||||||
|
"form": ddeHTMLFormElement;
|
||||||
|
"h1": ddeHTMLHeadingElement;
|
||||||
|
"h2": ddeHTMLHeadingElement;
|
||||||
|
"h3": ddeHTMLHeadingElement;
|
||||||
|
"h4": ddeHTMLHeadingElement;
|
||||||
|
"h5": ddeHTMLHeadingElement;
|
||||||
|
"h6": ddeHTMLHeadingElement;
|
||||||
|
"head": ddeHTMLHeadElement;
|
||||||
|
"hr": ddeHTMLHRElement;
|
||||||
|
"html": ddeHTMLHtmlElement;
|
||||||
|
"iframe": ddeHTMLIFrameElement;
|
||||||
|
"img": ddeHTMLImageElement;
|
||||||
|
"input": ddeHTMLInputElement;
|
||||||
|
"ins": ddeHTMLModElement;
|
||||||
|
"label": ddeHTMLLabelElement;
|
||||||
|
"legend": ddeHTMLLegendElement;
|
||||||
|
"li": ddeHTMLLIElement;
|
||||||
|
"link": ddeHTMLLinkElement;
|
||||||
|
"map": ddeHTMLMapElement;
|
||||||
|
"menu": ddeHTMLMenuElement;
|
||||||
|
"meta": ddeHTMLMetaElement;
|
||||||
|
"meter": ddeHTMLMeterElement;
|
||||||
|
"object": ddeHTMLObjectElement;
|
||||||
|
"ol": ddeHTMLOListElement;
|
||||||
|
"optgroup": ddeHTMLOptGroupElement;
|
||||||
|
"option": ddeHTMLOptionElement;
|
||||||
|
"output": ddeHTMLOutputElement;
|
||||||
|
"p": ddeHTMLParagraphElement;
|
||||||
|
"picture": ddeHTMLPictureElement;
|
||||||
|
"pre": ddeHTMLPreElement;
|
||||||
|
"progress": ddeHTMLProgressElement;
|
||||||
|
"q": ddeHTMLQuoteElement;
|
||||||
|
"script": ddeHTMLScriptElement;
|
||||||
|
"select": ddeHTMLSelectElement;
|
||||||
|
"slot": ddeHTMLSlotElement;
|
||||||
|
"source": ddeHTMLSourceElement;
|
||||||
|
"span": ddeHTMLSpanElement;
|
||||||
|
"style": ddeHTMLStyleElement;
|
||||||
|
"table": ddeHTMLTableElement;
|
||||||
|
"tbody": ddeHTMLTableSectionElement;
|
||||||
|
"td": ddeHTMLTableCellElement;
|
||||||
|
"template": ddeHTMLTemplateElement;
|
||||||
|
"textarea": ddeHTMLTextAreaElement;
|
||||||
|
"tfoot": ddeHTMLTableSectionElement;
|
||||||
|
"th": ddeHTMLTableCellElement;
|
||||||
|
"thead": ddeHTMLTableSectionElement;
|
||||||
|
"time": ddeHTMLTimeElement;
|
||||||
|
"title": ddeHTMLTitleElement;
|
||||||
|
"tr": ddeHTMLTableRowElement;
|
||||||
|
"track": ddeHTMLTrackElement;
|
||||||
|
"ul": ddeHTMLUListElement;
|
||||||
|
"video": ddeHTMLVideoElement;
|
||||||
|
}
|
||||||
|
interface ddeSVGElementTagNameMap {
|
||||||
|
"a": ddeSVGAElement;
|
||||||
|
"animate": ddeSVGAnimateElement;
|
||||||
|
"animateMotion": ddeSVGAnimateMotionElement;
|
||||||
|
"animateTransform": ddeSVGAnimateTransformElement;
|
||||||
|
"circle": ddeSVGCircleElement;
|
||||||
|
"clipPath": ddeSVGClipPathElement;
|
||||||
|
"defs": ddeSVGDefsElement;
|
||||||
|
"desc": ddeSVGDescElement;
|
||||||
|
"ellipse": ddeSVGEllipseElement;
|
||||||
|
"feBlend": ddeSVGFEBlendElement;
|
||||||
|
"feColorMatrix": ddeSVGFEColorMatrixElement;
|
||||||
|
"feComponentTransfer": ddeSVGFEComponentTransferElement;
|
||||||
|
"feComposite": ddeSVGFECompositeElement;
|
||||||
|
"feConvolveMatrix": ddeSVGFEConvolveMatrixElement;
|
||||||
|
"feDiffuseLighting": ddeSVGFEDiffuseLightingElement;
|
||||||
|
"feDisplacementMap": ddeSVGFEDisplacementMapElement;
|
||||||
|
"feDistantLight": ddeSVGFEDistantLightElement;
|
||||||
|
"feDropShadow": ddeSVGFEDropShadowElement;
|
||||||
|
"feFlood": ddeSVGFEFloodElement;
|
||||||
|
"feFuncA": ddeSVGFEFuncAElement;
|
||||||
|
"feFuncB": ddeSVGFEFuncBElement;
|
||||||
|
"feFuncG": ddeSVGFEFuncGElement;
|
||||||
|
"feFuncR": ddeSVGFEFuncRElement;
|
||||||
|
"feGaussianBlur": ddeSVGFEGaussianBlurElement;
|
||||||
|
"feImage": ddeSVGFEImageElement;
|
||||||
|
"feMerge": ddeSVGFEMergeElement;
|
||||||
|
"feMergeNode": ddeSVGFEMergeNodeElement;
|
||||||
|
"feMorphology": ddeSVGFEMorphologyElement;
|
||||||
|
"feOffset": ddeSVGFEOffsetElement;
|
||||||
|
"fePointLight": ddeSVGFEPointLightElement;
|
||||||
|
"feSpecularLighting": ddeSVGFESpecularLightingElement;
|
||||||
|
"feSpotLight": ddeSVGFESpotLightElement;
|
||||||
|
"feTile": ddeSVGFETileElement;
|
||||||
|
"feTurbulence": ddeSVGFETurbulenceElement;
|
||||||
|
"filter": ddeSVGFilterElement;
|
||||||
|
"foreignObject": ddeSVGForeignObjectElement;
|
||||||
|
"g": ddeSVGGElement;
|
||||||
|
"image": ddeSVGImageElement;
|
||||||
|
"line": ddeSVGLineElement;
|
||||||
|
"linearGradient": ddeSVGLinearGradientElement;
|
||||||
|
"marker": ddeSVGMarkerElement;
|
||||||
|
"mask": ddeSVGMaskElement;
|
||||||
|
"metadata": ddeSVGMetadataElement;
|
||||||
|
"mpath": ddeSVGMPathElement;
|
||||||
|
"path": ddeSVGPathElement;
|
||||||
|
"pattern": ddeSVGPatternElement;
|
||||||
|
"polygon": ddeSVGPolygonElement;
|
||||||
|
"polyline": ddeSVGPolylineElement;
|
||||||
|
"radialGradient": ddeSVGRadialGradientElement;
|
||||||
|
"rect": ddeSVGRectElement;
|
||||||
|
"script": ddeSVGScriptElement;
|
||||||
|
"set": ddeSVGSetElement;
|
||||||
|
"stop": ddeSVGStopElement;
|
||||||
|
"style": ddeSVGStyleElement;
|
||||||
|
"svg": ddeSVGSVGElement;
|
||||||
|
"switch": ddeSVGSwitchElement;
|
||||||
|
"symbol": ddeSVGSymbolElement;
|
||||||
|
"text": ddeSVGTextElement;
|
||||||
|
"textPath": ddeSVGTextPathElement;
|
||||||
|
"title": ddeSVGTitleElement;
|
||||||
|
"tspan": ddeSVGTSpanElement;
|
||||||
|
"use": ddeSVGUseElement;
|
||||||
|
"view": ddeSVGViewElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// editorconfig-checker-disable
|
||||||
|
export interface ddeHTMLAnchorElement extends HTMLAnchorElement {
|
||||||
|
append: ddeAppend<ddeHTMLAnchorElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLAreaElement extends HTMLAreaElement {
|
||||||
|
append: ddeAppend<ddeHTMLAreaElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLAudioElement extends HTMLAudioElement {
|
||||||
|
append: ddeAppend<ddeHTMLAudioElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLBaseElement extends HTMLBaseElement {
|
||||||
|
append: ddeAppend<ddeHTMLBaseElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLQuoteElement extends HTMLQuoteElement {
|
||||||
|
append: ddeAppend<ddeHTMLQuoteElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLBodyElement extends HTMLBodyElement {
|
||||||
|
append: ddeAppend<ddeHTMLBodyElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLBRElement extends HTMLBRElement {
|
||||||
|
append: ddeAppend<ddeHTMLBRElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLButtonElement extends HTMLButtonElement {
|
||||||
|
append: ddeAppend<ddeHTMLButtonElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLCanvasElement extends HTMLCanvasElement {
|
||||||
|
append: ddeAppend<ddeHTMLCanvasElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableCaptionElement extends HTMLTableCaptionElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableCaptionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableColElement extends HTMLTableColElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableColElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableColElement extends HTMLTableColElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableColElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDataElement extends HTMLDataElement {
|
||||||
|
append: ddeAppend<ddeHTMLDataElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDataListElement extends HTMLDataListElement {
|
||||||
|
append: ddeAppend<ddeHTMLDataListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLModElement extends HTMLModElement {
|
||||||
|
append: ddeAppend<ddeHTMLModElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDetailsElement extends HTMLDetailsElement {
|
||||||
|
append: ddeAppend<ddeHTMLDetailsElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDialogElement extends HTMLDialogElement {
|
||||||
|
append: ddeAppend<ddeHTMLDialogElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDivElement extends HTMLDivElement {
|
||||||
|
append: ddeAppend<ddeHTMLDivElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDListElement extends HTMLDListElement {
|
||||||
|
append: ddeAppend<ddeHTMLDListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLEmbedElement extends HTMLEmbedElement {
|
||||||
|
append: ddeAppend<ddeHTMLEmbedElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLFieldSetElement extends HTMLFieldSetElement {
|
||||||
|
append: ddeAppend<ddeHTMLFieldSetElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLFormElement extends HTMLFormElement {
|
||||||
|
append: ddeAppend<ddeHTMLFormElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHeadingElement extends HTMLHeadingElement {
|
||||||
|
append: ddeAppend<ddeHTMLHeadingElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHeadElement extends HTMLHeadElement {
|
||||||
|
append: ddeAppend<ddeHTMLHeadElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHRElement extends HTMLHRElement {
|
||||||
|
append: ddeAppend<ddeHTMLHRElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHtmlElement extends HTMLHtmlElement {
|
||||||
|
append: ddeAppend<ddeHTMLHtmlElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLIFrameElement extends HTMLIFrameElement {
|
||||||
|
append: ddeAppend<ddeHTMLIFrameElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLImageElement extends HTMLImageElement {
|
||||||
|
append: ddeAppend<ddeHTMLImageElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLInputElement extends HTMLInputElement {
|
||||||
|
append: ddeAppend<ddeHTMLInputElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLabelElement extends HTMLLabelElement {
|
||||||
|
append: ddeAppend<ddeHTMLLabelElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLegendElement extends HTMLLegendElement {
|
||||||
|
append: ddeAppend<ddeHTMLLegendElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLIElement extends HTMLLIElement {
|
||||||
|
append: ddeAppend<ddeHTMLLIElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLinkElement extends HTMLLinkElement {
|
||||||
|
append: ddeAppend<ddeHTMLLinkElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMapElement extends HTMLMapElement {
|
||||||
|
append: ddeAppend<ddeHTMLMapElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMenuElement extends HTMLMenuElement {
|
||||||
|
append: ddeAppend<ddeHTMLMenuElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMetaElement extends HTMLMetaElement {
|
||||||
|
append: ddeAppend<ddeHTMLMetaElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMeterElement extends HTMLMeterElement {
|
||||||
|
append: ddeAppend<ddeHTMLMeterElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLObjectElement extends HTMLObjectElement {
|
||||||
|
append: ddeAppend<ddeHTMLObjectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOListElement extends HTMLOListElement {
|
||||||
|
append: ddeAppend<ddeHTMLOListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOptGroupElement extends HTMLOptGroupElement {
|
||||||
|
append: ddeAppend<ddeHTMLOptGroupElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOptionElement extends HTMLOptionElement {
|
||||||
|
append: ddeAppend<ddeHTMLOptionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOutputElement extends HTMLOutputElement {
|
||||||
|
append: ddeAppend<ddeHTMLOutputElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLParagraphElement extends HTMLParagraphElement {
|
||||||
|
append: ddeAppend<ddeHTMLParagraphElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLPictureElement extends HTMLPictureElement {
|
||||||
|
append: ddeAppend<ddeHTMLPictureElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLPreElement extends HTMLPreElement {
|
||||||
|
append: ddeAppend<ddeHTMLPreElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLProgressElement extends HTMLProgressElement {
|
||||||
|
append: ddeAppend<ddeHTMLProgressElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLScriptElement extends HTMLScriptElement {
|
||||||
|
append: ddeAppend<ddeHTMLScriptElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSelectElement extends HTMLSelectElement {
|
||||||
|
append: ddeAppend<ddeHTMLSelectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSlotElement extends HTMLSlotElement {
|
||||||
|
append: ddeAppend<ddeHTMLSlotElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSourceElement extends HTMLSourceElement {
|
||||||
|
append: ddeAppend<ddeHTMLSourceElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSpanElement extends HTMLSpanElement {
|
||||||
|
append: ddeAppend<ddeHTMLSpanElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLStyleElement extends HTMLStyleElement {
|
||||||
|
append: ddeAppend<ddeHTMLStyleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableElement extends HTMLTableElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableSectionElement extends HTMLTableSectionElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableSectionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableCellElement extends HTMLTableCellElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableCellElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTemplateElement extends HTMLTemplateElement {
|
||||||
|
append: ddeAppend<ddeHTMLTemplateElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTextAreaElement extends HTMLTextAreaElement {
|
||||||
|
append: ddeAppend<ddeHTMLTextAreaElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableCellElement extends HTMLTableCellElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableCellElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTimeElement extends HTMLTimeElement {
|
||||||
|
append: ddeAppend<ddeHTMLTimeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTitleElement extends HTMLTitleElement {
|
||||||
|
append: ddeAppend<ddeHTMLTitleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableRowElement extends HTMLTableRowElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableRowElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTrackElement extends HTMLTrackElement {
|
||||||
|
append: ddeAppend<ddeHTMLTrackElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLUListElement extends HTMLUListElement {
|
||||||
|
append: ddeAppend<ddeHTMLUListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLVideoElement extends HTMLVideoElement {
|
||||||
|
append: ddeAppend<ddeHTMLVideoElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAElement extends SVGAElement {
|
||||||
|
append: ddeAppend<ddeSVGAElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAnimateElement extends SVGAnimateElement {
|
||||||
|
append: ddeAppend<ddeSVGAnimateElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAnimateMotionElement extends SVGAnimateMotionElement {
|
||||||
|
append: ddeAppend<ddeSVGAnimateMotionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAnimateTransformElement extends SVGAnimateTransformElement {
|
||||||
|
append: ddeAppend<ddeSVGAnimateTransformElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGCircleElement extends SVGCircleElement {
|
||||||
|
append: ddeAppend<ddeSVGCircleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGClipPathElement extends SVGClipPathElement {
|
||||||
|
append: ddeAppend<ddeSVGClipPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGDefsElement extends SVGDefsElement {
|
||||||
|
append: ddeAppend<ddeSVGDefsElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGDescElement extends SVGDescElement {
|
||||||
|
append: ddeAppend<ddeSVGDescElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGEllipseElement extends SVGEllipseElement {
|
||||||
|
append: ddeAppend<ddeSVGEllipseElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEBlendElement extends SVGFEBlendElement {
|
||||||
|
append: ddeAppend<ddeSVGFEBlendElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEColorMatrixElement extends SVGFEColorMatrixElement {
|
||||||
|
append: ddeAppend<ddeSVGFEColorMatrixElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEComponentTransferElement extends SVGFEComponentTransferElement {
|
||||||
|
append: ddeAppend<ddeSVGFEComponentTransferElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFECompositeElement extends SVGFECompositeElement {
|
||||||
|
append: ddeAppend<ddeSVGFECompositeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEConvolveMatrixElement extends SVGFEConvolveMatrixElement {
|
||||||
|
append: ddeAppend<ddeSVGFEConvolveMatrixElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDiffuseLightingElement extends SVGFEDiffuseLightingElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDiffuseLightingElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDisplacementMapElement extends SVGFEDisplacementMapElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDisplacementMapElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDistantLightElement extends SVGFEDistantLightElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDistantLightElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDropShadowElement extends SVGFEDropShadowElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDropShadowElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFloodElement extends SVGFEFloodElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFloodElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncAElement extends SVGFEFuncAElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncAElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncBElement extends SVGFEFuncBElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncBElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncGElement extends SVGFEFuncGElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncGElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncRElement extends SVGFEFuncRElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncRElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEGaussianBlurElement extends SVGFEGaussianBlurElement {
|
||||||
|
append: ddeAppend<ddeSVGFEGaussianBlurElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEImageElement extends SVGFEImageElement {
|
||||||
|
append: ddeAppend<ddeSVGFEImageElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEMergeElement extends SVGFEMergeElement {
|
||||||
|
append: ddeAppend<ddeSVGFEMergeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEMergeNodeElement extends SVGFEMergeNodeElement {
|
||||||
|
append: ddeAppend<ddeSVGFEMergeNodeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEMorphologyElement extends SVGFEMorphologyElement {
|
||||||
|
append: ddeAppend<ddeSVGFEMorphologyElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEOffsetElement extends SVGFEOffsetElement {
|
||||||
|
append: ddeAppend<ddeSVGFEOffsetElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEPointLightElement extends SVGFEPointLightElement {
|
||||||
|
append: ddeAppend<ddeSVGFEPointLightElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFESpecularLightingElement extends SVGFESpecularLightingElement {
|
||||||
|
append: ddeAppend<ddeSVGFESpecularLightingElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFESpotLightElement extends SVGFESpotLightElement {
|
||||||
|
append: ddeAppend<ddeSVGFESpotLightElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFETileElement extends SVGFETileElement {
|
||||||
|
append: ddeAppend<ddeSVGFETileElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFETurbulenceElement extends SVGFETurbulenceElement {
|
||||||
|
append: ddeAppend<ddeSVGFETurbulenceElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFilterElement extends SVGFilterElement {
|
||||||
|
append: ddeAppend<ddeSVGFilterElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGForeignObjectElement extends SVGForeignObjectElement {
|
||||||
|
append: ddeAppend<ddeSVGForeignObjectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGGElement extends SVGGElement {
|
||||||
|
append: ddeAppend<ddeSVGGElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGImageElement extends SVGImageElement {
|
||||||
|
append: ddeAppend<ddeSVGImageElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGLineElement extends SVGLineElement {
|
||||||
|
append: ddeAppend<ddeSVGLineElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGLinearGradientElement extends SVGLinearGradientElement {
|
||||||
|
append: ddeAppend<ddeSVGLinearGradientElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMarkerElement extends SVGMarkerElement {
|
||||||
|
append: ddeAppend<ddeSVGMarkerElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMaskElement extends SVGMaskElement {
|
||||||
|
append: ddeAppend<ddeSVGMaskElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMetadataElement extends SVGMetadataElement {
|
||||||
|
append: ddeAppend<ddeSVGMetadataElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMPathElement extends SVGMPathElement {
|
||||||
|
append: ddeAppend<ddeSVGMPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPathElement extends SVGPathElement {
|
||||||
|
append: ddeAppend<ddeSVGPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPatternElement extends SVGPatternElement {
|
||||||
|
append: ddeAppend<ddeSVGPatternElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPolygonElement extends SVGPolygonElement {
|
||||||
|
append: ddeAppend<ddeSVGPolygonElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPolylineElement extends SVGPolylineElement {
|
||||||
|
append: ddeAppend<ddeSVGPolylineElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGRadialGradientElement extends SVGRadialGradientElement {
|
||||||
|
append: ddeAppend<ddeSVGRadialGradientElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGRectElement extends SVGRectElement {
|
||||||
|
append: ddeAppend<ddeSVGRectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGScriptElement extends SVGScriptElement {
|
||||||
|
append: ddeAppend<ddeSVGScriptElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSetElement extends SVGSetElement {
|
||||||
|
append: ddeAppend<ddeSVGSetElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGStopElement extends SVGStopElement {
|
||||||
|
append: ddeAppend<ddeSVGStopElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGStyleElement extends SVGStyleElement {
|
||||||
|
append: ddeAppend<ddeSVGStyleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSVGElement extends SVGSVGElement {
|
||||||
|
append: ddeAppend<ddeSVGSVGElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSwitchElement extends SVGSwitchElement {
|
||||||
|
append: ddeAppend<ddeSVGSwitchElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSymbolElement extends SVGSymbolElement {
|
||||||
|
append: ddeAppend<ddeSVGSymbolElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTextElement extends SVGTextElement {
|
||||||
|
append: ddeAppend<ddeSVGTextElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTextPathElement extends SVGTextPathElement {
|
||||||
|
append: ddeAppend<ddeSVGTextPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTitleElement extends SVGTitleElement {
|
||||||
|
append: ddeAppend<ddeSVGTitleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTSpanElement extends SVGTSpanElement {
|
||||||
|
append: ddeAppend<ddeSVGTSpanElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGUseElement extends SVGUseElement {
|
||||||
|
append: ddeAppend<ddeSVGUseElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGViewElement extends SVGViewElement {
|
||||||
|
append: ddeAppend<ddeSVGViewElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
dispatchEvent$1 as dispatchEvent,
|
||||||
|
el as createElement,
|
||||||
|
elNS as createElementNS,
|
||||||
|
};
|
||||||
|
|
||||||
|
export as namespace iife;
|
||||||
|
|
||||||
|
export {};
|
3
dist/iife-with-signals.min.js
vendored
Normal file
3
dist/iife-with-signals.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
861
dist/iife.d.ts
vendored
Normal file
861
dist/iife.d.ts
vendored
Normal file
@ -0,0 +1,861 @@
|
|||||||
|
// Generated by dts-bundle-generator v9.5.1
|
||||||
|
|
||||||
|
export interface Signal<V, A> {
|
||||||
|
/** The current value of the signal */
|
||||||
|
get(): V;
|
||||||
|
/** Set new value of the signal */
|
||||||
|
set(value: V, force?: boolean): V;
|
||||||
|
toJSON(): V;
|
||||||
|
valueOf(): V;
|
||||||
|
}
|
||||||
|
export type Action<V> = (this: {
|
||||||
|
value: V;
|
||||||
|
stopPropagation(): void;
|
||||||
|
}, ...a: any[]) => typeof signal._ | void;
|
||||||
|
//type SymbolSignal= Symbol;
|
||||||
|
export type SymbolOnclear = symbol;
|
||||||
|
export type Actions<V> = Record<string | SymbolOnclear, Action<V>>;
|
||||||
|
export type OnListenerOptions = Pick<AddEventListenerOptions, "signal"> & {
|
||||||
|
first_time?: boolean;
|
||||||
|
};
|
||||||
|
export type SElement = Node | Element | DocumentFragment | ddeHTMLElement | ddeSVGElement | ddeDocumentFragment;
|
||||||
|
export interface signal {
|
||||||
|
_: Symbol;
|
||||||
|
/**
|
||||||
|
* Computations signal. This creates a signal which is computed from other signals.
|
||||||
|
* */
|
||||||
|
<V extends () => any>(computation: V): Signal<ReturnType<V>, {}>;
|
||||||
|
/**
|
||||||
|
* Simple example:
|
||||||
|
* ```js
|
||||||
|
* const hello= S("Hello Signal");
|
||||||
|
* ```
|
||||||
|
* …simple todo signal:
|
||||||
|
* ```js
|
||||||
|
* const todos= S([], {
|
||||||
|
* add(v){ this.value.push(S(v)); },
|
||||||
|
* remove(i){ this.value.splice(i, 1); },
|
||||||
|
* [S.symbols.onclear](){ S.clear(...this.value); },
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* …computed signal:
|
||||||
|
* ```js
|
||||||
|
* const name= S("Jan");
|
||||||
|
* const surname= S("Andrle");
|
||||||
|
* const fullname= S(()=> name.get()+" "+surname.get());
|
||||||
|
* ```
|
||||||
|
* @param value Initial signal value. Or function computing value from other signals.
|
||||||
|
* @param actions Use to define actions on the signal. Such as add item to the array.
|
||||||
|
* There is also a reserved function `S.symbol.onclear` which is called when the signal is cleared
|
||||||
|
* by `S.clear`.
|
||||||
|
* */
|
||||||
|
<V, A extends Actions<V>>(value: V, actions?: A): Signal<V, A>;
|
||||||
|
action<S extends Signal<any, Actions<any>>, A extends (S extends Signal<any, infer A> ? A : never), N extends keyof A>(signal: S, name: N, ...params: A[N] extends (...args: infer P) => any ? P : never): void;
|
||||||
|
clear(...signals: Signal<any, any>[]): void;
|
||||||
|
on<T>(signal: Signal<T, any>, onchange: (a: T) => void, options?: OnListenerOptions): void;
|
||||||
|
symbols: {
|
||||||
|
//signal: SymbolSignal;
|
||||||
|
onclear: SymbolOnclear;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Reactive element, which is rendered based on the given signal.
|
||||||
|
* ```js
|
||||||
|
* S.el(signal, value=> value ? el("b", "True") : el("i", "False"));
|
||||||
|
* S.el(listS, list=> list.map(li=> el("li", li)));
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
el<S extends any>(signal: Signal<S, any>, el: (v: S) => SElement | SElement[]): DocumentFragment;
|
||||||
|
observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>;
|
||||||
|
}
|
||||||
|
declare const signal: signal;
|
||||||
|
declare global {
|
||||||
|
type ddeSignal<T, A = {}> = Signal<T, A>;
|
||||||
|
type ddeAction<V> = Action<V>;
|
||||||
|
type ddeActions<V> = Actions<V>;
|
||||||
|
}
|
||||||
|
export type CustomElementTagNameMap = {
|
||||||
|
"#text": Text;
|
||||||
|
"#comment": Comment;
|
||||||
|
};
|
||||||
|
export type SupportedElement = HTMLElementTagNameMap[keyof HTMLElementTagNameMap] | SVGElementTagNameMap[keyof SVGElementTagNameMap] | MathMLElementTagNameMap[keyof MathMLElementTagNameMap] | CustomElementTagNameMap[keyof CustomElementTagNameMap];
|
||||||
|
declare global {
|
||||||
|
type ddeComponentAttributes = Record<any, any> | undefined;
|
||||||
|
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El, ...rest: any) => any;
|
||||||
|
type ddeString = string | Signal<string, {}>;
|
||||||
|
type ddeStringable = ddeString | number | Signal<number, {}>;
|
||||||
|
}
|
||||||
|
export type Host<EL extends SupportedElement> = (...addons: ddeElementAddon<EL>[]) => EL;
|
||||||
|
export type PascalCase = `${Capitalize<string>}${string}`;
|
||||||
|
export type AttrsModified = {
|
||||||
|
/**
|
||||||
|
* Use string like in HTML (internally uses `*.setAttribute("style", *)`), or object representation (like DOM API).
|
||||||
|
*/
|
||||||
|
style: Partial<CSSStyleDeclaration> | ddeString | Partial<{
|
||||||
|
[K in keyof CSSStyleDeclaration]: Signal<CSSStyleDeclaration[K], {}>;
|
||||||
|
}>;
|
||||||
|
/**
|
||||||
|
* Provide option to add/remove/toggle CSS clasess (index of object) using 1/0/-1.
|
||||||
|
* In fact `el.classList.toggle(class_name)` for `-1` and `el.classList.toggle(class_name, Boolean(...))`
|
||||||
|
* for others.
|
||||||
|
*/
|
||||||
|
classList: Record<string, -1 | 0 | 1 | boolean | Signal<-1 | 0 | 1 | boolean, {}>>;
|
||||||
|
/**
|
||||||
|
* Used by the dataset HTML attribute to represent data for custom attributes added to elements.
|
||||||
|
* Values are converted to string (see {@link DOMStringMap}).
|
||||||
|
*
|
||||||
|
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMStringMap)
|
||||||
|
* */
|
||||||
|
dataset: Record<string, ddeStringable>;
|
||||||
|
/**
|
||||||
|
* Sets `aria-*` simiraly to `dataset`
|
||||||
|
* */
|
||||||
|
ariaset: Record<string, ddeString>;
|
||||||
|
} & Record<`=${string}` | `data${PascalCase}` | `aria${PascalCase}`, ddeString> & Record<`.${string}`, any>;
|
||||||
|
export type _fromElsInterfaces<EL extends SupportedElement> = Omit<EL, keyof AttrsModified>;
|
||||||
|
export type IsReadonly<T, K extends keyof T> = T extends {
|
||||||
|
readonly [P in K]: T[K];
|
||||||
|
} ? true : false;
|
||||||
|
/**
|
||||||
|
* Just element attributtes
|
||||||
|
*
|
||||||
|
* In most cases, you can use native propertie such as
|
||||||
|
* [MDN WEB/API/Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) and so on
|
||||||
|
* (e.g. [`Text`](https://developer.mozilla.org/en-US/docs/Web/API/Text)).
|
||||||
|
*
|
||||||
|
* There is added support for `data[A-Z].*`/`aria[A-Z].*` to be converted to the kebab-case alternatives.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export type ElementAttributes<T extends SupportedElement> = Partial<{
|
||||||
|
[K in keyof _fromElsInterfaces<T>]: _fromElsInterfaces<T>[K] extends ((...p: any[]) => any) ? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>) => Signal<ReturnType<_fromElsInterfaces<T>[K]>, {}>) : (IsReadonly<_fromElsInterfaces<T>, K> extends false ? _fromElsInterfaces<T>[K] | Signal<_fromElsInterfaces<T>[K], {}> : ddeStringable);
|
||||||
|
} & AttrsModified> & Record<string, any>;
|
||||||
|
export function classListDeclarative<El extends SupportedElement>(element: El, classList: AttrsModified["classList"]): El;
|
||||||
|
export function assign<El extends SupportedElement>(element: El, ...attrs_array: ElementAttributes<El>[]): El;
|
||||||
|
export function assignAttribute<El extends SupportedElement, ATT extends keyof ElementAttributes<El>>(element: El, attr: ATT, value: ElementAttributes<El>[ATT]): ElementAttributes<El>[ATT];
|
||||||
|
export type ExtendedHTMLElementTagNameMap = HTMLElementTagNameMap & CustomElementTagNameMap;
|
||||||
|
export namespace el {
|
||||||
|
/**
|
||||||
|
* Creates a marker comment for elements
|
||||||
|
*
|
||||||
|
* @param attrs - Marker attributes
|
||||||
|
* @param [is_open=false] - Whether the marker is open-ended
|
||||||
|
* @returns Comment node marker
|
||||||
|
*/
|
||||||
|
export function mark(attrs: {
|
||||||
|
type: "component" | "reactive" | "later";
|
||||||
|
name?: string;
|
||||||
|
host?: "this" | "parentElement";
|
||||||
|
}, is_open?: boolean): Comment;
|
||||||
|
}
|
||||||
|
export function chainableAppend<EL extends SupportedElement>(el: EL): EL | ddeHTMLElement;
|
||||||
|
export function el<A extends ddeComponentAttributes, EL extends SupportedElement | ddeDocumentFragment>(component: (attr: A, ...rest: any[]) => EL, attrs?: NoInfer<A>, ...addons: ddeElementAddon<EL>[]): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? EL : (EL extends ddeDocumentFragment ? EL : ddeHTMLElement);
|
||||||
|
export function el<A extends {
|
||||||
|
textContent: ddeStringable;
|
||||||
|
}, EL extends SupportedElement | ddeDocumentFragment>(component: (attr: A, ...rest: any[]) => EL, attrs?: NoInfer<A>["textContent"], ...addons: ddeElementAddon<EL>[]): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? EL : (EL extends ddeDocumentFragment ? EL : ddeHTMLElement);
|
||||||
|
export function el<TAG extends keyof ExtendedHTMLElementTagNameMap>(tag_name: TAG, attrs?: ElementAttributes<ExtendedHTMLElementTagNameMap[NoInfer<TAG>]> | ddeStringable, ...addons: ddeElementAddon<ExtendedHTMLElementTagNameMap[NoInfer<TAG>]>[]): TAG extends keyof ddeHTMLElementTagNameMap ? ddeHTMLElementTagNameMap[TAG] : ddeHTMLElement;
|
||||||
|
export function el(tag_name?: "<>"): ddeDocumentFragment;
|
||||||
|
export function el(tag_name: string, attrs?: ElementAttributes<HTMLElement> | ddeStringable, ...addons: ddeElementAddon<HTMLElement>[]): ddeHTMLElement;
|
||||||
|
export function elNS(namespace: "http://www.w3.org/2000/svg"): <TAG extends keyof SVGElementTagNameMap & string, EL extends (TAG extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[TAG] : SVGElement)>(tag_name: TAG, attrs?: ElementAttributes<NoInfer<EL>> | ddeStringable, ...addons: ddeElementAddon<NoInfer<EL>>[]) => TAG extends keyof ddeSVGElementTagNameMap ? ddeSVGElementTagNameMap[TAG] : ddeSVGElement;
|
||||||
|
export function elNS(namespace: "http://www.w3.org/1998/Math/MathML"): <TAG extends keyof MathMLElementTagNameMap & string, EL extends (TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement)>(tag_name: TAG, attrs?: ddeStringable | Partial<{
|
||||||
|
[key in keyof EL]: EL[key] | Signal<EL[key], {}> | string | number | boolean;
|
||||||
|
}>, ...addons: ddeElementAddon<NoInfer<EL>>[]) => ddeMathMLElement;
|
||||||
|
export function elNS(namespace: string): (tag_name: string, attrs?: string | ddeStringable | Record<string, any>, ...addons: ddeElementAddon<SupportedElement>[]) => SupportedElement;
|
||||||
|
/** Simulate slots for ddeComponents */
|
||||||
|
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(root: EL): EL;
|
||||||
|
/**
|
||||||
|
* Simulate slots in Custom Elements without using `shadowRoot`.
|
||||||
|
* @param el Custom Element root element
|
||||||
|
* @param body Body of the custom element
|
||||||
|
* */
|
||||||
|
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(el: HTMLElement, body: EL): EL;
|
||||||
|
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, host: Host<SupportedElement>): (data?: any) => void;
|
||||||
|
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?: EventInit): (element: SupportedElement, data?: any) => void;
|
||||||
|
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
|
||||||
|
export interface On {
|
||||||
|
/** Listens to the DOM event. See {@link Document.addEventListener} */
|
||||||
|
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & {
|
||||||
|
target: EL;
|
||||||
|
}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
|
||||||
|
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
|
||||||
|
/** 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>;
|
||||||
|
/** 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>;
|
||||||
|
/**
|
||||||
|
* Fires after the next tick of the Javascript event loop.
|
||||||
|
* This is handy for example to apply some property depending on the element content:
|
||||||
|
* ```js
|
||||||
|
* const selected= "Z";
|
||||||
|
* //...
|
||||||
|
* return el("form").append(
|
||||||
|
* el("select", null, on.defer(e=> e.value=selected)).append(
|
||||||
|
* el("option", { value: "A", textContent: "A" }),
|
||||||
|
* //...
|
||||||
|
* el("option", { value: "Z", textContent: "Z" }),
|
||||||
|
* ),
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>;
|
||||||
|
}
|
||||||
|
export const on: On;
|
||||||
|
export type Scope = {
|
||||||
|
scope: Node | Function | Object;
|
||||||
|
host: Host<SupportedElement>;
|
||||||
|
custom_element: false | HTMLElement;
|
||||||
|
prevent: boolean;
|
||||||
|
};
|
||||||
|
/** Current scope created last time the `el(Function)` was invoke. (Or {@link scope.push}) */
|
||||||
|
export const scope: {
|
||||||
|
current: Scope;
|
||||||
|
/** Stops all automatizations. E. g. signals used as attributes in current scope
|
||||||
|
* registers removing these listeners (and clean signal if no other listeners are detected)
|
||||||
|
* on `disconnected` event. */
|
||||||
|
preventDefault<T extends boolean>(prevent: T): T;
|
||||||
|
/**
|
||||||
|
* This represents reference to the current host element — `scope.host()`.
|
||||||
|
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
||||||
|
* — `scope.host(on.connected(console.log))`.
|
||||||
|
* */
|
||||||
|
host: Host<SupportedElement>;
|
||||||
|
/**
|
||||||
|
* Creates/gets an AbortController that triggers when the element disconnects
|
||||||
|
* */
|
||||||
|
signal: AbortSignal;
|
||||||
|
state: Scope[];
|
||||||
|
/** Adds new child scope. All attributes are inherited by default. */
|
||||||
|
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>;
|
||||||
|
/** Adds root scope as a child of the current scope. */
|
||||||
|
pushRoot(): ReturnType<Array<Scope>["push"]>;
|
||||||
|
/** Removes last/current child scope. */
|
||||||
|
pop(): ReturnType<Array<Scope>["pop"]>;
|
||||||
|
};
|
||||||
|
export function customElementRender<EL extends HTMLElement, P extends any = Record<string, string | Signal<string, {}>>>(target: ShadowRoot | EL, render: (props: P) => SupportedElement | DocumentFragment, props?: P | ((el: EL) => P)): EL;
|
||||||
|
export function customElementWithDDE<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||||
|
export function lifecyclesToEvents<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||||
|
/**
|
||||||
|
* This is used primarly for server side rendering. To be sure that all async operations
|
||||||
|
* are finished before the page is sent to the client.
|
||||||
|
* ```
|
||||||
|
* // on component
|
||||||
|
* function component(){
|
||||||
|
* …
|
||||||
|
* queue(fetch(...).then(...));
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // building the page
|
||||||
|
* async function build(){
|
||||||
|
* const { component }= await import("./component.js");
|
||||||
|
* document.body.append(el(component));
|
||||||
|
* await queue();
|
||||||
|
* retutn document.body.innerHTML;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
export function queue(promise?: Promise<unknown>): Promise<unknown>;
|
||||||
|
/**
|
||||||
|
* Memoization utility for caching DOM elements to improve performance.
|
||||||
|
* Used to prevent unnecessary recreation of elements when rendering lists or complex components.
|
||||||
|
*
|
||||||
|
* @param key - Unique identifier for the element (usually an ID or unique value)
|
||||||
|
* @param generator - Function that creates the element
|
||||||
|
* @returns The cached element if the key exists, otherwise the result of the generator function
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* // Within S.el for list rendering
|
||||||
|
* S.el(itemsSignal, (items, memo) =>
|
||||||
|
* el("ul").append(
|
||||||
|
* ...items.map(item =>
|
||||||
|
* memo(item.id, () => el(ItemComponent, item))
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function memo<T>(key: string | number | object, generator: (key: any) => T): T;
|
||||||
|
/**
|
||||||
|
* Memo namespace containing utility functions for memoization.
|
||||||
|
*/
|
||||||
|
export namespace memo {
|
||||||
|
/**
|
||||||
|
* Checks if an object is a memo scope.
|
||||||
|
* @param obj - The object to check
|
||||||
|
* @returns True if the object is a memo scope
|
||||||
|
*/
|
||||||
|
export function isScope(obj: any): boolean;
|
||||||
|
/**
|
||||||
|
* Creates a memoized function with optional cleanup support.
|
||||||
|
*
|
||||||
|
* @param fun - The function to memoize
|
||||||
|
* @param options - Configuration options
|
||||||
|
* @param options.signal - AbortSignal for cleanup
|
||||||
|
* @param options.onlyLast - When true, only keeps the cache from the most recent call
|
||||||
|
* @returns A memoized version of the function with a .clear() method
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const renderItems = memo.scope(function(items) {
|
||||||
|
* return items.map(item =>
|
||||||
|
* memo(item.id, () => el("div", item.name))
|
||||||
|
* );
|
||||||
|
* }, {
|
||||||
|
* signal: controller.signal,
|
||||||
|
* onlyLast: true
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function scope<F extends Function>(fun: F, options?: {
|
||||||
|
signal?: AbortSignal;
|
||||||
|
onlyLast?: boolean;
|
||||||
|
}): F & {
|
||||||
|
clear: () => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/* TypeScript MEH */
|
||||||
|
declare global {
|
||||||
|
type ddeAppend<el> = (...nodes: (Node | string)[]) => el;
|
||||||
|
interface ddeDocumentFragment extends DocumentFragment {
|
||||||
|
append: ddeAppend<ddeDocumentFragment>;
|
||||||
|
}
|
||||||
|
interface ddeHTMLElement extends HTMLElement {
|
||||||
|
append: ddeAppend<ddeHTMLElement>;
|
||||||
|
}
|
||||||
|
interface ddeSVGElement extends SVGElement {
|
||||||
|
append: ddeAppend<ddeSVGElement>;
|
||||||
|
}
|
||||||
|
interface ddeMathMLElement extends MathMLElement {
|
||||||
|
append: ddeAppend<ddeMathMLElement>;
|
||||||
|
}
|
||||||
|
interface ddeHTMLElementTagNameMap {
|
||||||
|
"a": ddeHTMLAnchorElement;
|
||||||
|
"area": ddeHTMLAreaElement;
|
||||||
|
"audio": ddeHTMLAudioElement;
|
||||||
|
"base": ddeHTMLBaseElement;
|
||||||
|
"blockquote": ddeHTMLQuoteElement;
|
||||||
|
"body": ddeHTMLBodyElement;
|
||||||
|
"br": ddeHTMLBRElement;
|
||||||
|
"button": ddeHTMLButtonElement;
|
||||||
|
"canvas": ddeHTMLCanvasElement;
|
||||||
|
"caption": ddeHTMLTableCaptionElement;
|
||||||
|
"col": ddeHTMLTableColElement;
|
||||||
|
"colgroup": ddeHTMLTableColElement;
|
||||||
|
"data": ddeHTMLDataElement;
|
||||||
|
"datalist": ddeHTMLDataListElement;
|
||||||
|
"del": ddeHTMLModElement;
|
||||||
|
"details": ddeHTMLDetailsElement;
|
||||||
|
"dialog": ddeHTMLDialogElement;
|
||||||
|
"div": ddeHTMLDivElement;
|
||||||
|
"dl": ddeHTMLDListElement;
|
||||||
|
"embed": ddeHTMLEmbedElement;
|
||||||
|
"fieldset": ddeHTMLFieldSetElement;
|
||||||
|
"form": ddeHTMLFormElement;
|
||||||
|
"h1": ddeHTMLHeadingElement;
|
||||||
|
"h2": ddeHTMLHeadingElement;
|
||||||
|
"h3": ddeHTMLHeadingElement;
|
||||||
|
"h4": ddeHTMLHeadingElement;
|
||||||
|
"h5": ddeHTMLHeadingElement;
|
||||||
|
"h6": ddeHTMLHeadingElement;
|
||||||
|
"head": ddeHTMLHeadElement;
|
||||||
|
"hr": ddeHTMLHRElement;
|
||||||
|
"html": ddeHTMLHtmlElement;
|
||||||
|
"iframe": ddeHTMLIFrameElement;
|
||||||
|
"img": ddeHTMLImageElement;
|
||||||
|
"input": ddeHTMLInputElement;
|
||||||
|
"ins": ddeHTMLModElement;
|
||||||
|
"label": ddeHTMLLabelElement;
|
||||||
|
"legend": ddeHTMLLegendElement;
|
||||||
|
"li": ddeHTMLLIElement;
|
||||||
|
"link": ddeHTMLLinkElement;
|
||||||
|
"map": ddeHTMLMapElement;
|
||||||
|
"menu": ddeHTMLMenuElement;
|
||||||
|
"meta": ddeHTMLMetaElement;
|
||||||
|
"meter": ddeHTMLMeterElement;
|
||||||
|
"object": ddeHTMLObjectElement;
|
||||||
|
"ol": ddeHTMLOListElement;
|
||||||
|
"optgroup": ddeHTMLOptGroupElement;
|
||||||
|
"option": ddeHTMLOptionElement;
|
||||||
|
"output": ddeHTMLOutputElement;
|
||||||
|
"p": ddeHTMLParagraphElement;
|
||||||
|
"picture": ddeHTMLPictureElement;
|
||||||
|
"pre": ddeHTMLPreElement;
|
||||||
|
"progress": ddeHTMLProgressElement;
|
||||||
|
"q": ddeHTMLQuoteElement;
|
||||||
|
"script": ddeHTMLScriptElement;
|
||||||
|
"select": ddeHTMLSelectElement;
|
||||||
|
"slot": ddeHTMLSlotElement;
|
||||||
|
"source": ddeHTMLSourceElement;
|
||||||
|
"span": ddeHTMLSpanElement;
|
||||||
|
"style": ddeHTMLStyleElement;
|
||||||
|
"table": ddeHTMLTableElement;
|
||||||
|
"tbody": ddeHTMLTableSectionElement;
|
||||||
|
"td": ddeHTMLTableCellElement;
|
||||||
|
"template": ddeHTMLTemplateElement;
|
||||||
|
"textarea": ddeHTMLTextAreaElement;
|
||||||
|
"tfoot": ddeHTMLTableSectionElement;
|
||||||
|
"th": ddeHTMLTableCellElement;
|
||||||
|
"thead": ddeHTMLTableSectionElement;
|
||||||
|
"time": ddeHTMLTimeElement;
|
||||||
|
"title": ddeHTMLTitleElement;
|
||||||
|
"tr": ddeHTMLTableRowElement;
|
||||||
|
"track": ddeHTMLTrackElement;
|
||||||
|
"ul": ddeHTMLUListElement;
|
||||||
|
"video": ddeHTMLVideoElement;
|
||||||
|
}
|
||||||
|
interface ddeSVGElementTagNameMap {
|
||||||
|
"a": ddeSVGAElement;
|
||||||
|
"animate": ddeSVGAnimateElement;
|
||||||
|
"animateMotion": ddeSVGAnimateMotionElement;
|
||||||
|
"animateTransform": ddeSVGAnimateTransformElement;
|
||||||
|
"circle": ddeSVGCircleElement;
|
||||||
|
"clipPath": ddeSVGClipPathElement;
|
||||||
|
"defs": ddeSVGDefsElement;
|
||||||
|
"desc": ddeSVGDescElement;
|
||||||
|
"ellipse": ddeSVGEllipseElement;
|
||||||
|
"feBlend": ddeSVGFEBlendElement;
|
||||||
|
"feColorMatrix": ddeSVGFEColorMatrixElement;
|
||||||
|
"feComponentTransfer": ddeSVGFEComponentTransferElement;
|
||||||
|
"feComposite": ddeSVGFECompositeElement;
|
||||||
|
"feConvolveMatrix": ddeSVGFEConvolveMatrixElement;
|
||||||
|
"feDiffuseLighting": ddeSVGFEDiffuseLightingElement;
|
||||||
|
"feDisplacementMap": ddeSVGFEDisplacementMapElement;
|
||||||
|
"feDistantLight": ddeSVGFEDistantLightElement;
|
||||||
|
"feDropShadow": ddeSVGFEDropShadowElement;
|
||||||
|
"feFlood": ddeSVGFEFloodElement;
|
||||||
|
"feFuncA": ddeSVGFEFuncAElement;
|
||||||
|
"feFuncB": ddeSVGFEFuncBElement;
|
||||||
|
"feFuncG": ddeSVGFEFuncGElement;
|
||||||
|
"feFuncR": ddeSVGFEFuncRElement;
|
||||||
|
"feGaussianBlur": ddeSVGFEGaussianBlurElement;
|
||||||
|
"feImage": ddeSVGFEImageElement;
|
||||||
|
"feMerge": ddeSVGFEMergeElement;
|
||||||
|
"feMergeNode": ddeSVGFEMergeNodeElement;
|
||||||
|
"feMorphology": ddeSVGFEMorphologyElement;
|
||||||
|
"feOffset": ddeSVGFEOffsetElement;
|
||||||
|
"fePointLight": ddeSVGFEPointLightElement;
|
||||||
|
"feSpecularLighting": ddeSVGFESpecularLightingElement;
|
||||||
|
"feSpotLight": ddeSVGFESpotLightElement;
|
||||||
|
"feTile": ddeSVGFETileElement;
|
||||||
|
"feTurbulence": ddeSVGFETurbulenceElement;
|
||||||
|
"filter": ddeSVGFilterElement;
|
||||||
|
"foreignObject": ddeSVGForeignObjectElement;
|
||||||
|
"g": ddeSVGGElement;
|
||||||
|
"image": ddeSVGImageElement;
|
||||||
|
"line": ddeSVGLineElement;
|
||||||
|
"linearGradient": ddeSVGLinearGradientElement;
|
||||||
|
"marker": ddeSVGMarkerElement;
|
||||||
|
"mask": ddeSVGMaskElement;
|
||||||
|
"metadata": ddeSVGMetadataElement;
|
||||||
|
"mpath": ddeSVGMPathElement;
|
||||||
|
"path": ddeSVGPathElement;
|
||||||
|
"pattern": ddeSVGPatternElement;
|
||||||
|
"polygon": ddeSVGPolygonElement;
|
||||||
|
"polyline": ddeSVGPolylineElement;
|
||||||
|
"radialGradient": ddeSVGRadialGradientElement;
|
||||||
|
"rect": ddeSVGRectElement;
|
||||||
|
"script": ddeSVGScriptElement;
|
||||||
|
"set": ddeSVGSetElement;
|
||||||
|
"stop": ddeSVGStopElement;
|
||||||
|
"style": ddeSVGStyleElement;
|
||||||
|
"svg": ddeSVGSVGElement;
|
||||||
|
"switch": ddeSVGSwitchElement;
|
||||||
|
"symbol": ddeSVGSymbolElement;
|
||||||
|
"text": ddeSVGTextElement;
|
||||||
|
"textPath": ddeSVGTextPathElement;
|
||||||
|
"title": ddeSVGTitleElement;
|
||||||
|
"tspan": ddeSVGTSpanElement;
|
||||||
|
"use": ddeSVGUseElement;
|
||||||
|
"view": ddeSVGViewElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// editorconfig-checker-disable
|
||||||
|
export interface ddeHTMLAnchorElement extends HTMLAnchorElement {
|
||||||
|
append: ddeAppend<ddeHTMLAnchorElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLAreaElement extends HTMLAreaElement {
|
||||||
|
append: ddeAppend<ddeHTMLAreaElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLAudioElement extends HTMLAudioElement {
|
||||||
|
append: ddeAppend<ddeHTMLAudioElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLBaseElement extends HTMLBaseElement {
|
||||||
|
append: ddeAppend<ddeHTMLBaseElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLQuoteElement extends HTMLQuoteElement {
|
||||||
|
append: ddeAppend<ddeHTMLQuoteElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLBodyElement extends HTMLBodyElement {
|
||||||
|
append: ddeAppend<ddeHTMLBodyElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLBRElement extends HTMLBRElement {
|
||||||
|
append: ddeAppend<ddeHTMLBRElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLButtonElement extends HTMLButtonElement {
|
||||||
|
append: ddeAppend<ddeHTMLButtonElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLCanvasElement extends HTMLCanvasElement {
|
||||||
|
append: ddeAppend<ddeHTMLCanvasElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableCaptionElement extends HTMLTableCaptionElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableCaptionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableColElement extends HTMLTableColElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableColElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableColElement extends HTMLTableColElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableColElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDataElement extends HTMLDataElement {
|
||||||
|
append: ddeAppend<ddeHTMLDataElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDataListElement extends HTMLDataListElement {
|
||||||
|
append: ddeAppend<ddeHTMLDataListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLModElement extends HTMLModElement {
|
||||||
|
append: ddeAppend<ddeHTMLModElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDetailsElement extends HTMLDetailsElement {
|
||||||
|
append: ddeAppend<ddeHTMLDetailsElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDialogElement extends HTMLDialogElement {
|
||||||
|
append: ddeAppend<ddeHTMLDialogElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDivElement extends HTMLDivElement {
|
||||||
|
append: ddeAppend<ddeHTMLDivElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDListElement extends HTMLDListElement {
|
||||||
|
append: ddeAppend<ddeHTMLDListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLEmbedElement extends HTMLEmbedElement {
|
||||||
|
append: ddeAppend<ddeHTMLEmbedElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLFieldSetElement extends HTMLFieldSetElement {
|
||||||
|
append: ddeAppend<ddeHTMLFieldSetElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLFormElement extends HTMLFormElement {
|
||||||
|
append: ddeAppend<ddeHTMLFormElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHeadingElement extends HTMLHeadingElement {
|
||||||
|
append: ddeAppend<ddeHTMLHeadingElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHeadElement extends HTMLHeadElement {
|
||||||
|
append: ddeAppend<ddeHTMLHeadElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHRElement extends HTMLHRElement {
|
||||||
|
append: ddeAppend<ddeHTMLHRElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHtmlElement extends HTMLHtmlElement {
|
||||||
|
append: ddeAppend<ddeHTMLHtmlElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLIFrameElement extends HTMLIFrameElement {
|
||||||
|
append: ddeAppend<ddeHTMLIFrameElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLImageElement extends HTMLImageElement {
|
||||||
|
append: ddeAppend<ddeHTMLImageElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLInputElement extends HTMLInputElement {
|
||||||
|
append: ddeAppend<ddeHTMLInputElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLabelElement extends HTMLLabelElement {
|
||||||
|
append: ddeAppend<ddeHTMLLabelElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLegendElement extends HTMLLegendElement {
|
||||||
|
append: ddeAppend<ddeHTMLLegendElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLIElement extends HTMLLIElement {
|
||||||
|
append: ddeAppend<ddeHTMLLIElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLinkElement extends HTMLLinkElement {
|
||||||
|
append: ddeAppend<ddeHTMLLinkElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMapElement extends HTMLMapElement {
|
||||||
|
append: ddeAppend<ddeHTMLMapElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMenuElement extends HTMLMenuElement {
|
||||||
|
append: ddeAppend<ddeHTMLMenuElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMetaElement extends HTMLMetaElement {
|
||||||
|
append: ddeAppend<ddeHTMLMetaElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMeterElement extends HTMLMeterElement {
|
||||||
|
append: ddeAppend<ddeHTMLMeterElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLObjectElement extends HTMLObjectElement {
|
||||||
|
append: ddeAppend<ddeHTMLObjectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOListElement extends HTMLOListElement {
|
||||||
|
append: ddeAppend<ddeHTMLOListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOptGroupElement extends HTMLOptGroupElement {
|
||||||
|
append: ddeAppend<ddeHTMLOptGroupElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOptionElement extends HTMLOptionElement {
|
||||||
|
append: ddeAppend<ddeHTMLOptionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOutputElement extends HTMLOutputElement {
|
||||||
|
append: ddeAppend<ddeHTMLOutputElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLParagraphElement extends HTMLParagraphElement {
|
||||||
|
append: ddeAppend<ddeHTMLParagraphElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLPictureElement extends HTMLPictureElement {
|
||||||
|
append: ddeAppend<ddeHTMLPictureElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLPreElement extends HTMLPreElement {
|
||||||
|
append: ddeAppend<ddeHTMLPreElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLProgressElement extends HTMLProgressElement {
|
||||||
|
append: ddeAppend<ddeHTMLProgressElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLScriptElement extends HTMLScriptElement {
|
||||||
|
append: ddeAppend<ddeHTMLScriptElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSelectElement extends HTMLSelectElement {
|
||||||
|
append: ddeAppend<ddeHTMLSelectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSlotElement extends HTMLSlotElement {
|
||||||
|
append: ddeAppend<ddeHTMLSlotElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSourceElement extends HTMLSourceElement {
|
||||||
|
append: ddeAppend<ddeHTMLSourceElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSpanElement extends HTMLSpanElement {
|
||||||
|
append: ddeAppend<ddeHTMLSpanElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLStyleElement extends HTMLStyleElement {
|
||||||
|
append: ddeAppend<ddeHTMLStyleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableElement extends HTMLTableElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableSectionElement extends HTMLTableSectionElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableSectionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableCellElement extends HTMLTableCellElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableCellElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTemplateElement extends HTMLTemplateElement {
|
||||||
|
append: ddeAppend<ddeHTMLTemplateElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTextAreaElement extends HTMLTextAreaElement {
|
||||||
|
append: ddeAppend<ddeHTMLTextAreaElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableCellElement extends HTMLTableCellElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableCellElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTimeElement extends HTMLTimeElement {
|
||||||
|
append: ddeAppend<ddeHTMLTimeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTitleElement extends HTMLTitleElement {
|
||||||
|
append: ddeAppend<ddeHTMLTitleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableRowElement extends HTMLTableRowElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableRowElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTrackElement extends HTMLTrackElement {
|
||||||
|
append: ddeAppend<ddeHTMLTrackElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLUListElement extends HTMLUListElement {
|
||||||
|
append: ddeAppend<ddeHTMLUListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLVideoElement extends HTMLVideoElement {
|
||||||
|
append: ddeAppend<ddeHTMLVideoElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAElement extends SVGAElement {
|
||||||
|
append: ddeAppend<ddeSVGAElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAnimateElement extends SVGAnimateElement {
|
||||||
|
append: ddeAppend<ddeSVGAnimateElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAnimateMotionElement extends SVGAnimateMotionElement {
|
||||||
|
append: ddeAppend<ddeSVGAnimateMotionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAnimateTransformElement extends SVGAnimateTransformElement {
|
||||||
|
append: ddeAppend<ddeSVGAnimateTransformElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGCircleElement extends SVGCircleElement {
|
||||||
|
append: ddeAppend<ddeSVGCircleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGClipPathElement extends SVGClipPathElement {
|
||||||
|
append: ddeAppend<ddeSVGClipPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGDefsElement extends SVGDefsElement {
|
||||||
|
append: ddeAppend<ddeSVGDefsElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGDescElement extends SVGDescElement {
|
||||||
|
append: ddeAppend<ddeSVGDescElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGEllipseElement extends SVGEllipseElement {
|
||||||
|
append: ddeAppend<ddeSVGEllipseElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEBlendElement extends SVGFEBlendElement {
|
||||||
|
append: ddeAppend<ddeSVGFEBlendElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEColorMatrixElement extends SVGFEColorMatrixElement {
|
||||||
|
append: ddeAppend<ddeSVGFEColorMatrixElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEComponentTransferElement extends SVGFEComponentTransferElement {
|
||||||
|
append: ddeAppend<ddeSVGFEComponentTransferElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFECompositeElement extends SVGFECompositeElement {
|
||||||
|
append: ddeAppend<ddeSVGFECompositeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEConvolveMatrixElement extends SVGFEConvolveMatrixElement {
|
||||||
|
append: ddeAppend<ddeSVGFEConvolveMatrixElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDiffuseLightingElement extends SVGFEDiffuseLightingElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDiffuseLightingElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDisplacementMapElement extends SVGFEDisplacementMapElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDisplacementMapElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDistantLightElement extends SVGFEDistantLightElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDistantLightElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDropShadowElement extends SVGFEDropShadowElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDropShadowElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFloodElement extends SVGFEFloodElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFloodElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncAElement extends SVGFEFuncAElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncAElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncBElement extends SVGFEFuncBElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncBElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncGElement extends SVGFEFuncGElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncGElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncRElement extends SVGFEFuncRElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncRElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEGaussianBlurElement extends SVGFEGaussianBlurElement {
|
||||||
|
append: ddeAppend<ddeSVGFEGaussianBlurElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEImageElement extends SVGFEImageElement {
|
||||||
|
append: ddeAppend<ddeSVGFEImageElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEMergeElement extends SVGFEMergeElement {
|
||||||
|
append: ddeAppend<ddeSVGFEMergeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEMergeNodeElement extends SVGFEMergeNodeElement {
|
||||||
|
append: ddeAppend<ddeSVGFEMergeNodeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEMorphologyElement extends SVGFEMorphologyElement {
|
||||||
|
append: ddeAppend<ddeSVGFEMorphologyElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEOffsetElement extends SVGFEOffsetElement {
|
||||||
|
append: ddeAppend<ddeSVGFEOffsetElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEPointLightElement extends SVGFEPointLightElement {
|
||||||
|
append: ddeAppend<ddeSVGFEPointLightElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFESpecularLightingElement extends SVGFESpecularLightingElement {
|
||||||
|
append: ddeAppend<ddeSVGFESpecularLightingElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFESpotLightElement extends SVGFESpotLightElement {
|
||||||
|
append: ddeAppend<ddeSVGFESpotLightElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFETileElement extends SVGFETileElement {
|
||||||
|
append: ddeAppend<ddeSVGFETileElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFETurbulenceElement extends SVGFETurbulenceElement {
|
||||||
|
append: ddeAppend<ddeSVGFETurbulenceElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFilterElement extends SVGFilterElement {
|
||||||
|
append: ddeAppend<ddeSVGFilterElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGForeignObjectElement extends SVGForeignObjectElement {
|
||||||
|
append: ddeAppend<ddeSVGForeignObjectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGGElement extends SVGGElement {
|
||||||
|
append: ddeAppend<ddeSVGGElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGImageElement extends SVGImageElement {
|
||||||
|
append: ddeAppend<ddeSVGImageElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGLineElement extends SVGLineElement {
|
||||||
|
append: ddeAppend<ddeSVGLineElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGLinearGradientElement extends SVGLinearGradientElement {
|
||||||
|
append: ddeAppend<ddeSVGLinearGradientElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMarkerElement extends SVGMarkerElement {
|
||||||
|
append: ddeAppend<ddeSVGMarkerElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMaskElement extends SVGMaskElement {
|
||||||
|
append: ddeAppend<ddeSVGMaskElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMetadataElement extends SVGMetadataElement {
|
||||||
|
append: ddeAppend<ddeSVGMetadataElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMPathElement extends SVGMPathElement {
|
||||||
|
append: ddeAppend<ddeSVGMPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPathElement extends SVGPathElement {
|
||||||
|
append: ddeAppend<ddeSVGPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPatternElement extends SVGPatternElement {
|
||||||
|
append: ddeAppend<ddeSVGPatternElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPolygonElement extends SVGPolygonElement {
|
||||||
|
append: ddeAppend<ddeSVGPolygonElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPolylineElement extends SVGPolylineElement {
|
||||||
|
append: ddeAppend<ddeSVGPolylineElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGRadialGradientElement extends SVGRadialGradientElement {
|
||||||
|
append: ddeAppend<ddeSVGRadialGradientElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGRectElement extends SVGRectElement {
|
||||||
|
append: ddeAppend<ddeSVGRectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGScriptElement extends SVGScriptElement {
|
||||||
|
append: ddeAppend<ddeSVGScriptElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSetElement extends SVGSetElement {
|
||||||
|
append: ddeAppend<ddeSVGSetElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGStopElement extends SVGStopElement {
|
||||||
|
append: ddeAppend<ddeSVGStopElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGStyleElement extends SVGStyleElement {
|
||||||
|
append: ddeAppend<ddeSVGStyleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSVGElement extends SVGSVGElement {
|
||||||
|
append: ddeAppend<ddeSVGSVGElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSwitchElement extends SVGSwitchElement {
|
||||||
|
append: ddeAppend<ddeSVGSwitchElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSymbolElement extends SVGSymbolElement {
|
||||||
|
append: ddeAppend<ddeSVGSymbolElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTextElement extends SVGTextElement {
|
||||||
|
append: ddeAppend<ddeSVGTextElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTextPathElement extends SVGTextPathElement {
|
||||||
|
append: ddeAppend<ddeSVGTextPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTitleElement extends SVGTitleElement {
|
||||||
|
append: ddeAppend<ddeSVGTitleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTSpanElement extends SVGTSpanElement {
|
||||||
|
append: ddeAppend<ddeSVGTSpanElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGUseElement extends SVGUseElement {
|
||||||
|
append: ddeAppend<ddeSVGUseElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGViewElement extends SVGViewElement {
|
||||||
|
append: ddeAppend<ddeSVGViewElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
dispatchEvent$1 as dispatchEvent,
|
||||||
|
el as createElement,
|
||||||
|
elNS as createElementNS,
|
||||||
|
};
|
||||||
|
|
||||||
|
export as namespace iife;
|
||||||
|
|
||||||
|
export {};
|
703
dist/iife.js
vendored
Normal file
703
dist/iife.js
vendored
Normal file
@ -0,0 +1,703 @@
|
|||||||
|
var DDE = (() => {
|
||||||
|
var __defProp = Object.defineProperty;
|
||||||
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||||
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||||
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||||
|
var __export = (target, all) => {
|
||||||
|
for (var name in all)
|
||||||
|
__defProp(target, name, { get: all[name], enumerable: true });
|
||||||
|
};
|
||||||
|
var __copyProps = (to, from, except, desc) => {
|
||||||
|
if (from && typeof from === "object" || typeof from === "function") {
|
||||||
|
for (let key of __getOwnPropNames(from))
|
||||||
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||||
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||||
|
}
|
||||||
|
return to;
|
||||||
|
};
|
||||||
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||||
|
|
||||||
|
// index.js
|
||||||
|
var index_exports = {};
|
||||||
|
__export(index_exports, {
|
||||||
|
assign: () => assign,
|
||||||
|
assignAttribute: () => assignAttribute,
|
||||||
|
chainableAppend: () => chainableAppend,
|
||||||
|
classListDeclarative: () => classListDeclarative,
|
||||||
|
createElement: () => createElement,
|
||||||
|
createElementNS: () => createElementNS,
|
||||||
|
customElementRender: () => customElementRender,
|
||||||
|
customElementWithDDE: () => lifecyclesToEvents,
|
||||||
|
dispatchEvent: () => dispatchEvent,
|
||||||
|
el: () => createElement,
|
||||||
|
elNS: () => createElementNS,
|
||||||
|
lifecyclesToEvents: () => lifecyclesToEvents,
|
||||||
|
memo: () => memo,
|
||||||
|
on: () => on,
|
||||||
|
queue: () => queue,
|
||||||
|
registerReactivity: () => registerReactivity,
|
||||||
|
scope: () => scope,
|
||||||
|
simulateSlots: () => simulateSlots
|
||||||
|
});
|
||||||
|
|
||||||
|
// src/helpers.js
|
||||||
|
var hasOwn = (...a) => Object.prototype.hasOwnProperty.call(...a);
|
||||||
|
function isUndef(value) {
|
||||||
|
return typeof value === "undefined";
|
||||||
|
}
|
||||||
|
function isInstance(obj, cls) {
|
||||||
|
return obj instanceof cls;
|
||||||
|
}
|
||||||
|
function isProtoFrom(obj, cls) {
|
||||||
|
return Object.prototype.isPrototypeOf.call(cls, obj);
|
||||||
|
}
|
||||||
|
function oCreate(proto = null, p = {}) {
|
||||||
|
return Object.create(proto, p);
|
||||||
|
}
|
||||||
|
function oAssign(...o) {
|
||||||
|
return Object.assign(...o);
|
||||||
|
}
|
||||||
|
function onAbort(signal, listener) {
|
||||||
|
if (!signal || !isInstance(signal, AbortSignal))
|
||||||
|
return true;
|
||||||
|
if (signal.aborted)
|
||||||
|
return;
|
||||||
|
signal.addEventListener("abort", listener);
|
||||||
|
return function cleanUp() {
|
||||||
|
signal.removeEventListener("abort", listener);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function requestIdle() {
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
(globalThis.requestIdleCallback || requestAnimationFrame)(resolve);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/dom-lib/common.js
|
||||||
|
var enviroment = {
|
||||||
|
setDeleteAttr,
|
||||||
|
ssr: "",
|
||||||
|
D: globalThis.document,
|
||||||
|
N: globalThis.Node,
|
||||||
|
F: globalThis.DocumentFragment,
|
||||||
|
H: globalThis.HTMLElement,
|
||||||
|
S: globalThis.SVGElement,
|
||||||
|
M: globalThis.MutationObserver,
|
||||||
|
q: (p) => p || Promise.resolve()
|
||||||
|
};
|
||||||
|
function setDeleteAttr(obj, prop, val) {
|
||||||
|
Reflect.set(obj, prop, val);
|
||||||
|
if (!isUndef(val)) return;
|
||||||
|
Reflect.deleteProperty(obj, prop);
|
||||||
|
if (isInstance(obj, enviroment.H) && obj.getAttribute(prop) === "undefined")
|
||||||
|
return obj.removeAttribute(prop);
|
||||||
|
if (Reflect.get(obj, prop) === "undefined")
|
||||||
|
return Reflect.set(obj, prop, "");
|
||||||
|
}
|
||||||
|
var keyLTE = "__dde_lifecyclesToEvents";
|
||||||
|
var evc = "dde:connected";
|
||||||
|
var evd = "dde:disconnected";
|
||||||
|
var eva = "dde:attributeChanged";
|
||||||
|
|
||||||
|
// src/dom-lib/events-observer.js
|
||||||
|
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
|
||||||
|
get() {
|
||||||
|
return () => {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
function connectionsChangesObserverConstructor() {
|
||||||
|
const store = /* @__PURE__ */ new Map();
|
||||||
|
let is_observing = false;
|
||||||
|
const observerListener = (stop2) => function(mutations) {
|
||||||
|
for (const mutation of mutations) {
|
||||||
|
if (mutation.type !== "childList") continue;
|
||||||
|
if (observerAdded(mutation.addedNodes, true)) {
|
||||||
|
stop2();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (observerRemoved(mutation.removedNodes, true))
|
||||||
|
stop2();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const observer = new enviroment.M(observerListener(stop));
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Creates an observer for a specific element
|
||||||
|
* @param {Element} element - Element to observe
|
||||||
|
* @returns {Function} Cleanup function
|
||||||
|
*/
|
||||||
|
observe(element) {
|
||||||
|
const o = new enviroment.M(observerListener(() => {
|
||||||
|
}));
|
||||||
|
o.observe(element, { childList: true, subtree: true });
|
||||||
|
return () => o.disconnect();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Register a connection listener for an element
|
||||||
|
* @param {Element} element - Element to watch
|
||||||
|
* @param {Function} listener - Callback for connection event
|
||||||
|
*/
|
||||||
|
onConnected(element, listener) {
|
||||||
|
start();
|
||||||
|
const listeners = getElementStore(element);
|
||||||
|
if (listeners.connected.has(listener)) return;
|
||||||
|
listeners.connected.add(listener);
|
||||||
|
listeners.length_c += 1;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Unregister a connection listener
|
||||||
|
* @param {Element} element - Element being watched
|
||||||
|
* @param {Function} listener - Callback to remove
|
||||||
|
*/
|
||||||
|
offConnected(element, listener) {
|
||||||
|
if (!store.has(element)) return;
|
||||||
|
const ls = store.get(element);
|
||||||
|
if (!ls.connected.has(listener)) return;
|
||||||
|
ls.connected.delete(listener);
|
||||||
|
ls.length_c -= 1;
|
||||||
|
cleanWhenOff(element, ls);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Register a disconnection listener for an element
|
||||||
|
* @param {Element} element - Element to watch
|
||||||
|
* @param {Function} listener - Callback for disconnection event
|
||||||
|
*/
|
||||||
|
onDisconnected(element, listener) {
|
||||||
|
start();
|
||||||
|
const listeners = getElementStore(element);
|
||||||
|
if (listeners.disconnected.has(listener)) return;
|
||||||
|
listeners.disconnected.add(listener);
|
||||||
|
listeners.length_d += 1;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Unregister a disconnection listener
|
||||||
|
* @param {Element} element - Element being watched
|
||||||
|
* @param {Function} listener - Callback to remove
|
||||||
|
*/
|
||||||
|
offDisconnected(element, listener) {
|
||||||
|
if (!store.has(element)) return;
|
||||||
|
const ls = store.get(element);
|
||||||
|
ls.disconnected.delete(listener);
|
||||||
|
ls.length_d -= 1;
|
||||||
|
cleanWhenOff(element, ls);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function cleanWhenOff(element, ls) {
|
||||||
|
if (ls.length_c || ls.length_d)
|
||||||
|
return;
|
||||||
|
store.delete(element);
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
function getElementStore(element) {
|
||||||
|
if (store.has(element)) return store.get(element);
|
||||||
|
const out = {
|
||||||
|
connected: /* @__PURE__ */ new WeakSet(),
|
||||||
|
length_c: 0,
|
||||||
|
disconnected: /* @__PURE__ */ new WeakSet(),
|
||||||
|
length_d: 0
|
||||||
|
};
|
||||||
|
store.set(element, out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
function start() {
|
||||||
|
if (is_observing) return;
|
||||||
|
is_observing = true;
|
||||||
|
observer.observe(enviroment.D.body, { childList: true, subtree: true });
|
||||||
|
}
|
||||||
|
function stop() {
|
||||||
|
if (!is_observing || store.size) return;
|
||||||
|
is_observing = false;
|
||||||
|
observer.disconnect();
|
||||||
|
}
|
||||||
|
async function collectChildren(element) {
|
||||||
|
if (store.size > 30)
|
||||||
|
await requestIdle();
|
||||||
|
const out = [];
|
||||||
|
if (!isInstance(element, enviroment.N)) return out;
|
||||||
|
for (const el of store.keys()) {
|
||||||
|
if (el === element || !isInstance(el, enviroment.N)) continue;
|
||||||
|
if (element.contains(el))
|
||||||
|
out.push(el);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
function observerAdded(addedNodes, is_root) {
|
||||||
|
let out = false;
|
||||||
|
for (const element of addedNodes) {
|
||||||
|
if (is_root) collectChildren(element).then(observerAdded);
|
||||||
|
if (!store.has(element)) continue;
|
||||||
|
const ls = store.get(element);
|
||||||
|
if (!ls.length_c) continue;
|
||||||
|
element.dispatchEvent(new Event(evc));
|
||||||
|
ls.connected = /* @__PURE__ */ new WeakSet();
|
||||||
|
ls.length_c = 0;
|
||||||
|
if (!ls.length_d) store.delete(element);
|
||||||
|
out = true;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
function observerRemoved(removedNodes, is_root) {
|
||||||
|
let out = false;
|
||||||
|
for (const element of removedNodes) {
|
||||||
|
if (is_root) collectChildren(element).then(observerRemoved);
|
||||||
|
if (!store.has(element)) continue;
|
||||||
|
const ls = store.get(element);
|
||||||
|
if (!ls.length_d) continue;
|
||||||
|
(globalThis.queueMicrotask || setTimeout)(dispatchRemove(element));
|
||||||
|
out = true;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
function dispatchRemove(element) {
|
||||||
|
return () => {
|
||||||
|
if (element.isConnected) return;
|
||||||
|
element.dispatchEvent(new Event(evd));
|
||||||
|
store.delete(element);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/dom-lib/events.js
|
||||||
|
function dispatchEvent(name, options, host) {
|
||||||
|
if (typeof options === "function") {
|
||||||
|
host = options;
|
||||||
|
options = null;
|
||||||
|
}
|
||||||
|
if (!options) options = {};
|
||||||
|
return function dispatch(element, ...d) {
|
||||||
|
if (host) {
|
||||||
|
d.unshift(element);
|
||||||
|
element = typeof host === "function" ? host() : host;
|
||||||
|
}
|
||||||
|
const event = d.length ? new CustomEvent(name, oAssign({ detail: d[0] }, options)) : new Event(name, options);
|
||||||
|
return element.dispatchEvent(event);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function on(event, listener, options) {
|
||||||
|
return function registerElement(element) {
|
||||||
|
element.addEventListener(event, listener, options);
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
on.defer = (fn) => setTimeout.bind(null, fn, 0);
|
||||||
|
var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true });
|
||||||
|
on.connected = function(listener, options) {
|
||||||
|
options = lifeOptions(options);
|
||||||
|
return function registerElement(element) {
|
||||||
|
element.addEventListener(evc, listener, options);
|
||||||
|
if (element[keyLTE]) return element;
|
||||||
|
if (element.isConnected) return element.dispatchEvent(new Event(evc)), element;
|
||||||
|
const c = onAbort(options.signal, () => c_ch_o.offConnected(element, listener));
|
||||||
|
if (c) c_ch_o.onConnected(element, listener);
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
on.disconnected = function(listener, options) {
|
||||||
|
options = lifeOptions(options);
|
||||||
|
return function registerElement(element) {
|
||||||
|
element.addEventListener(evd, listener, options);
|
||||||
|
if (element[keyLTE]) return element;
|
||||||
|
const c = onAbort(options.signal, () => c_ch_o.offDisconnected(element, listener));
|
||||||
|
if (c) c_ch_o.onDisconnected(element, listener);
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/dom-lib/scopes.js
|
||||||
|
var scopes = [{
|
||||||
|
get scope() {
|
||||||
|
return enviroment.D.body;
|
||||||
|
},
|
||||||
|
host: (c) => c ? c(enviroment.D.body) : enviroment.D.body,
|
||||||
|
prevent: true
|
||||||
|
}];
|
||||||
|
var store_abort = /* @__PURE__ */ new WeakMap();
|
||||||
|
var 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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
this.appendOriginal(...els);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
function chainableAppend(el) {
|
||||||
|
if (el.append === append) return el;
|
||||||
|
el.appendOriginal = el.append;
|
||||||
|
el.append = append;
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
var namespace;
|
||||||
|
function createElement(tag, attributes, ...addons) {
|
||||||
|
const s = signals(this);
|
||||||
|
let scoped = 0;
|
||||||
|
let el, el_host;
|
||||||
|
const att_type = typeof attributes;
|
||||||
|
if (att_type === "string" || att_type === "number" || s.isSignal(attributes))
|
||||||
|
attributes = { textContent: attributes };
|
||||||
|
switch (true) {
|
||||||
|
case typeof tag === "function": {
|
||||||
|
scoped = 1;
|
||||||
|
const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0);
|
||||||
|
scope.push({ scope: tag, host });
|
||||||
|
el = /** @type {Element} */
|
||||||
|
tag(attributes || void 0);
|
||||||
|
if (el.nodeName === "#comment") break;
|
||||||
|
const is_fragment = isInstance(el, enviroment.F);
|
||||||
|
const el_mark = createElement.mark({
|
||||||
|
type: "component",
|
||||||
|
name: tag.name,
|
||||||
|
host: is_fragment ? "this" : "parentElement"
|
||||||
|
});
|
||||||
|
el.prepend(el_mark);
|
||||||
|
if (is_fragment) el_host = el_mark;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case tag === "#text":
|
||||||
|
el = assign.call(this, enviroment.D.createTextNode(""), attributes);
|
||||||
|
break;
|
||||||
|
case (tag === "<>" || !tag):
|
||||||
|
el = assign.call(this, enviroment.D.createDocumentFragment(), attributes);
|
||||||
|
break;
|
||||||
|
case Boolean(namespace):
|
||||||
|
el = assign.call(this, enviroment.D.createElementNS(namespace, tag), attributes);
|
||||||
|
break;
|
||||||
|
case !el:
|
||||||
|
el = assign.call(this, enviroment.D.createElement(tag), attributes);
|
||||||
|
}
|
||||||
|
chainableAppend(el);
|
||||||
|
if (!el_host) el_host = el;
|
||||||
|
addons.forEach((c) => c(el_host));
|
||||||
|
if (scoped) scope.pop();
|
||||||
|
scoped = 2;
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
createElement.mark = function(attrs, is_open = false) {
|
||||||
|
attrs = Object.entries(attrs).map(([n, v]) => n + `="${v}"`).join(" ");
|
||||||
|
const end = is_open ? "" : "/";
|
||||||
|
const out = enviroment.D.createComment(`<dde:mark ${attrs}${enviroment.ssr}${end}>`);
|
||||||
|
if (is_open) out.end = enviroment.D.createComment("</dde:mark>");
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
function createElementNS(ns) {
|
||||||
|
const _this = this;
|
||||||
|
return function createElementNSCurried(...rest) {
|
||||||
|
namespace = ns;
|
||||||
|
const el = createElement.call(_this, ...rest);
|
||||||
|
namespace = void 0;
|
||||||
|
return el;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
var assign_context = /* @__PURE__ */ new WeakMap();
|
||||||
|
var { setDeleteAttr: setDeleteAttr2 } = enviroment;
|
||||||
|
function assign(element, ...attributes) {
|
||||||
|
if (!attributes.length) return element;
|
||||||
|
assign_context.set(element, assignContext(element, this));
|
||||||
|
for (const [key, value] of Object.entries(oAssign({}, ...attributes)))
|
||||||
|
assignAttribute.call(this, element, key, value);
|
||||||
|
assign_context.delete(element);
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
function assignAttribute(element, key, value) {
|
||||||
|
const { setRemoveAttr, s } = assignContext(element, this);
|
||||||
|
const _this = this;
|
||||||
|
value = s.processReactiveAttribute(
|
||||||
|
element,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
(key2, value2) => assignAttribute.call(_this, element, key2, value2)
|
||||||
|
);
|
||||||
|
const [k] = key;
|
||||||
|
if ("=" === k) return setRemoveAttr(key.slice(1), value);
|
||||||
|
if ("." === k) return setDelete(element, key.slice(1), value);
|
||||||
|
if (/(aria|data)([A-Z])/.test(key)) {
|
||||||
|
key = key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
||||||
|
return setRemoveAttr(key, value);
|
||||||
|
}
|
||||||
|
if ("className" === key) key = "class";
|
||||||
|
switch (key) {
|
||||||
|
case "xlink:href":
|
||||||
|
return setRemoveAttr(key, value, "http://www.w3.org/1999/xlink");
|
||||||
|
case "textContent":
|
||||||
|
return setDeleteAttr2(element, key, value);
|
||||||
|
case "style":
|
||||||
|
if (typeof value !== "object") break;
|
||||||
|
/* falls through */
|
||||||
|
case "dataset":
|
||||||
|
return forEachEntries(s, key, element, value, setDelete.bind(null, element[key]));
|
||||||
|
case "ariaset":
|
||||||
|
return forEachEntries(s, key, element, value, (key2, val) => setRemoveAttr("aria-" + key2, val));
|
||||||
|
case "classList":
|
||||||
|
return classListDeclarative.call(_this, element, value);
|
||||||
|
}
|
||||||
|
return isPropSetter(element, key) ? setDeleteAttr2(element, key, value) : setRemoveAttr(key, value);
|
||||||
|
}
|
||||||
|
function assignContext(element, _this) {
|
||||||
|
if (assign_context.has(element)) return assign_context.get(element);
|
||||||
|
const is_svg = isInstance(element, enviroment.S);
|
||||||
|
const setRemoveAttr = (is_svg ? setRemoveNS : setRemove).bind(null, element, "Attribute");
|
||||||
|
const s = signals(_this);
|
||||||
|
return { setRemoveAttr, s };
|
||||||
|
}
|
||||||
|
function classListDeclarative(element, toggle) {
|
||||||
|
const s = signals(this);
|
||||||
|
forEachEntries(
|
||||||
|
s,
|
||||||
|
"classList",
|
||||||
|
element,
|
||||||
|
toggle,
|
||||||
|
(class_name, val) => element.classList.toggle(class_name, val === -1 ? void 0 : Boolean(val))
|
||||||
|
);
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
function isPropSetter(el, key) {
|
||||||
|
if (!(key in el)) return false;
|
||||||
|
const des = getPropDescriptor(el, key);
|
||||||
|
return !isUndef(des.set);
|
||||||
|
}
|
||||||
|
function getPropDescriptor(p, key) {
|
||||||
|
p = Object.getPrototypeOf(p);
|
||||||
|
if (!p) return {};
|
||||||
|
const des = Object.getOwnPropertyDescriptor(p, key);
|
||||||
|
if (!des) return getPropDescriptor(p, key);
|
||||||
|
return des;
|
||||||
|
}
|
||||||
|
function forEachEntries(s, target, element, obj, cb) {
|
||||||
|
const S = String;
|
||||||
|
if (typeof obj !== "object" || obj === null) return;
|
||||||
|
return Object.entries(obj).forEach(function process([key, val]) {
|
||||||
|
if (!key) return;
|
||||||
|
key = new S(key);
|
||||||
|
key.target = target;
|
||||||
|
val = s.processReactiveAttribute(element, key, val, cb);
|
||||||
|
cb(key, val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/dom-lib/customElement.js
|
||||||
|
function simulateSlots(element, root = element) {
|
||||||
|
const mark_e = "\xB9\u2070", mark_s = "\u2713";
|
||||||
|
const slots = Object.fromEntries(
|
||||||
|
Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s])
|
||||||
|
);
|
||||||
|
element.append = new Proxy(element.append, {
|
||||||
|
apply(orig, _, els) {
|
||||||
|
if (els[0] === root) return orig.apply(element, els);
|
||||||
|
for (const el of els) {
|
||||||
|
const name = (el.slot || "") + mark_e;
|
||||||
|
try {
|
||||||
|
elementAttribute(el, "remove", "slot");
|
||||||
|
} catch (_error) {
|
||||||
|
}
|
||||||
|
const slot = slots[name];
|
||||||
|
if (!slot) return;
|
||||||
|
if (!slot.name.startsWith(mark_s)) {
|
||||||
|
slot.childNodes.forEach((c) => c.remove());
|
||||||
|
slot.name = mark_s + name;
|
||||||
|
}
|
||||||
|
slot.append(el);
|
||||||
|
}
|
||||||
|
element.append = orig;
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (element !== root) {
|
||||||
|
const els = Array.from(element.childNodes);
|
||||||
|
element.append(...els);
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
function customElementRender(target, render, props = {}) {
|
||||||
|
const custom_element = target.host || target;
|
||||||
|
scope.push({
|
||||||
|
scope: custom_element,
|
||||||
|
host: (...c) => c.length ? c.forEach((c2) => c2(custom_element)) : custom_element
|
||||||
|
});
|
||||||
|
if (typeof props === "function") props = props.call(custom_element, custom_element);
|
||||||
|
const is_lte = custom_element[keyLTE];
|
||||||
|
if (!is_lte) lifecyclesToEvents(custom_element);
|
||||||
|
const out = render.call(custom_element, props);
|
||||||
|
if (!is_lte) custom_element.dispatchEvent(new Event(evc));
|
||||||
|
if (target.nodeType === 11 && typeof target.mode === "string")
|
||||||
|
custom_element.addEventListener(evd, c_ch_o.observe(target), { once: true });
|
||||||
|
scope.pop();
|
||||||
|
return target.append(out);
|
||||||
|
}
|
||||||
|
function lifecyclesToEvents(class_declaration) {
|
||||||
|
wrapMethod(class_declaration.prototype, "connectedCallback", function(target, thisArg, detail) {
|
||||||
|
target.apply(thisArg, detail);
|
||||||
|
thisArg.dispatchEvent(new Event(evc));
|
||||||
|
});
|
||||||
|
wrapMethod(class_declaration.prototype, "disconnectedCallback", function(target, thisArg, detail) {
|
||||||
|
target.apply(thisArg, detail);
|
||||||
|
(globalThis.queueMicrotask || setTimeout)(
|
||||||
|
() => !thisArg.isConnected && thisArg.dispatchEvent(new Event(evd))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
wrapMethod(class_declaration.prototype, "attributeChangedCallback", function(target, thisArg, detail) {
|
||||||
|
const [attribute, , value] = detail;
|
||||||
|
thisArg.dispatchEvent(new CustomEvent(eva, {
|
||||||
|
detail: [attribute, value]
|
||||||
|
}));
|
||||||
|
target.apply(thisArg, detail);
|
||||||
|
});
|
||||||
|
class_declaration.prototype[keyLTE] = true;
|
||||||
|
return class_declaration;
|
||||||
|
}
|
||||||
|
function wrapMethod(obj, method, apply) {
|
||||||
|
obj[method] = new Proxy(obj[method] || (() => {
|
||||||
|
}), { apply });
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/memo.js
|
||||||
|
var memoMark = "__dde_memo";
|
||||||
|
var memo_scope = [];
|
||||||
|
function memo(key, generator) {
|
||||||
|
if (!memo_scope.length) return generator(key);
|
||||||
|
const k = typeof key === "object" ? JSON.stringify(key) : key;
|
||||||
|
const [{ cache, after }] = memo_scope;
|
||||||
|
return after(k, hasOwn(cache, k) ? cache[k] : generator(key));
|
||||||
|
}
|
||||||
|
memo.isScope = function(obj) {
|
||||||
|
return obj[memoMark];
|
||||||
|
};
|
||||||
|
memo.scope = function memoScopeCreate(fun, { signal, onlyLast } = {}) {
|
||||||
|
let cache = oCreate();
|
||||||
|
function memoScope(...args) {
|
||||||
|
if (signal && signal.aborted)
|
||||||
|
return fun.apply(this, args);
|
||||||
|
let cache_local = onlyLast ? cache : oCreate();
|
||||||
|
memo_scope.unshift({
|
||||||
|
cache,
|
||||||
|
after(key, val) {
|
||||||
|
return cache_local[key] = val;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const out = fun.apply(this, args);
|
||||||
|
memo_scope.shift();
|
||||||
|
cache = cache_local;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
memoScope[memoMark] = true;
|
||||||
|
memoScope.clear = () => cache = oCreate();
|
||||||
|
if (signal) signal.addEventListener("abort", memoScope.clear);
|
||||||
|
return memoScope;
|
||||||
|
};
|
||||||
|
return __toCommonJS(index_exports);
|
||||||
|
})();
|
861
dist/iife.min.d.ts
vendored
Normal file
861
dist/iife.min.d.ts
vendored
Normal file
@ -0,0 +1,861 @@
|
|||||||
|
// Generated by dts-bundle-generator v9.5.1
|
||||||
|
|
||||||
|
export interface Signal<V, A> {
|
||||||
|
/** The current value of the signal */
|
||||||
|
get(): V;
|
||||||
|
/** Set new value of the signal */
|
||||||
|
set(value: V, force?: boolean): V;
|
||||||
|
toJSON(): V;
|
||||||
|
valueOf(): V;
|
||||||
|
}
|
||||||
|
export type Action<V> = (this: {
|
||||||
|
value: V;
|
||||||
|
stopPropagation(): void;
|
||||||
|
}, ...a: any[]) => typeof signal._ | void;
|
||||||
|
//type SymbolSignal= Symbol;
|
||||||
|
export type SymbolOnclear = symbol;
|
||||||
|
export type Actions<V> = Record<string | SymbolOnclear, Action<V>>;
|
||||||
|
export type OnListenerOptions = Pick<AddEventListenerOptions, "signal"> & {
|
||||||
|
first_time?: boolean;
|
||||||
|
};
|
||||||
|
export type SElement = Node | Element | DocumentFragment | ddeHTMLElement | ddeSVGElement | ddeDocumentFragment;
|
||||||
|
export interface signal {
|
||||||
|
_: Symbol;
|
||||||
|
/**
|
||||||
|
* Computations signal. This creates a signal which is computed from other signals.
|
||||||
|
* */
|
||||||
|
<V extends () => any>(computation: V): Signal<ReturnType<V>, {}>;
|
||||||
|
/**
|
||||||
|
* Simple example:
|
||||||
|
* ```js
|
||||||
|
* const hello= S("Hello Signal");
|
||||||
|
* ```
|
||||||
|
* …simple todo signal:
|
||||||
|
* ```js
|
||||||
|
* const todos= S([], {
|
||||||
|
* add(v){ this.value.push(S(v)); },
|
||||||
|
* remove(i){ this.value.splice(i, 1); },
|
||||||
|
* [S.symbols.onclear](){ S.clear(...this.value); },
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* …computed signal:
|
||||||
|
* ```js
|
||||||
|
* const name= S("Jan");
|
||||||
|
* const surname= S("Andrle");
|
||||||
|
* const fullname= S(()=> name.get()+" "+surname.get());
|
||||||
|
* ```
|
||||||
|
* @param value Initial signal value. Or function computing value from other signals.
|
||||||
|
* @param actions Use to define actions on the signal. Such as add item to the array.
|
||||||
|
* There is also a reserved function `S.symbol.onclear` which is called when the signal is cleared
|
||||||
|
* by `S.clear`.
|
||||||
|
* */
|
||||||
|
<V, A extends Actions<V>>(value: V, actions?: A): Signal<V, A>;
|
||||||
|
action<S extends Signal<any, Actions<any>>, A extends (S extends Signal<any, infer A> ? A : never), N extends keyof A>(signal: S, name: N, ...params: A[N] extends (...args: infer P) => any ? P : never): void;
|
||||||
|
clear(...signals: Signal<any, any>[]): void;
|
||||||
|
on<T>(signal: Signal<T, any>, onchange: (a: T) => void, options?: OnListenerOptions): void;
|
||||||
|
symbols: {
|
||||||
|
//signal: SymbolSignal;
|
||||||
|
onclear: SymbolOnclear;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Reactive element, which is rendered based on the given signal.
|
||||||
|
* ```js
|
||||||
|
* S.el(signal, value=> value ? el("b", "True") : el("i", "False"));
|
||||||
|
* S.el(listS, list=> list.map(li=> el("li", li)));
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
el<S extends any>(signal: Signal<S, any>, el: (v: S) => SElement | SElement[]): DocumentFragment;
|
||||||
|
observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>;
|
||||||
|
}
|
||||||
|
declare const signal: signal;
|
||||||
|
declare global {
|
||||||
|
type ddeSignal<T, A = {}> = Signal<T, A>;
|
||||||
|
type ddeAction<V> = Action<V>;
|
||||||
|
type ddeActions<V> = Actions<V>;
|
||||||
|
}
|
||||||
|
export type CustomElementTagNameMap = {
|
||||||
|
"#text": Text;
|
||||||
|
"#comment": Comment;
|
||||||
|
};
|
||||||
|
export type SupportedElement = HTMLElementTagNameMap[keyof HTMLElementTagNameMap] | SVGElementTagNameMap[keyof SVGElementTagNameMap] | MathMLElementTagNameMap[keyof MathMLElementTagNameMap] | CustomElementTagNameMap[keyof CustomElementTagNameMap];
|
||||||
|
declare global {
|
||||||
|
type ddeComponentAttributes = Record<any, any> | undefined;
|
||||||
|
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El, ...rest: any) => any;
|
||||||
|
type ddeString = string | Signal<string, {}>;
|
||||||
|
type ddeStringable = ddeString | number | Signal<number, {}>;
|
||||||
|
}
|
||||||
|
export type Host<EL extends SupportedElement> = (...addons: ddeElementAddon<EL>[]) => EL;
|
||||||
|
export type PascalCase = `${Capitalize<string>}${string}`;
|
||||||
|
export type AttrsModified = {
|
||||||
|
/**
|
||||||
|
* Use string like in HTML (internally uses `*.setAttribute("style", *)`), or object representation (like DOM API).
|
||||||
|
*/
|
||||||
|
style: Partial<CSSStyleDeclaration> | ddeString | Partial<{
|
||||||
|
[K in keyof CSSStyleDeclaration]: Signal<CSSStyleDeclaration[K], {}>;
|
||||||
|
}>;
|
||||||
|
/**
|
||||||
|
* Provide option to add/remove/toggle CSS clasess (index of object) using 1/0/-1.
|
||||||
|
* In fact `el.classList.toggle(class_name)` for `-1` and `el.classList.toggle(class_name, Boolean(...))`
|
||||||
|
* for others.
|
||||||
|
*/
|
||||||
|
classList: Record<string, -1 | 0 | 1 | boolean | Signal<-1 | 0 | 1 | boolean, {}>>;
|
||||||
|
/**
|
||||||
|
* Used by the dataset HTML attribute to represent data for custom attributes added to elements.
|
||||||
|
* Values are converted to string (see {@link DOMStringMap}).
|
||||||
|
*
|
||||||
|
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMStringMap)
|
||||||
|
* */
|
||||||
|
dataset: Record<string, ddeStringable>;
|
||||||
|
/**
|
||||||
|
* Sets `aria-*` simiraly to `dataset`
|
||||||
|
* */
|
||||||
|
ariaset: Record<string, ddeString>;
|
||||||
|
} & Record<`=${string}` | `data${PascalCase}` | `aria${PascalCase}`, ddeString> & Record<`.${string}`, any>;
|
||||||
|
export type _fromElsInterfaces<EL extends SupportedElement> = Omit<EL, keyof AttrsModified>;
|
||||||
|
export type IsReadonly<T, K extends keyof T> = T extends {
|
||||||
|
readonly [P in K]: T[K];
|
||||||
|
} ? true : false;
|
||||||
|
/**
|
||||||
|
* Just element attributtes
|
||||||
|
*
|
||||||
|
* In most cases, you can use native propertie such as
|
||||||
|
* [MDN WEB/API/Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) and so on
|
||||||
|
* (e.g. [`Text`](https://developer.mozilla.org/en-US/docs/Web/API/Text)).
|
||||||
|
*
|
||||||
|
* There is added support for `data[A-Z].*`/`aria[A-Z].*` to be converted to the kebab-case alternatives.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export type ElementAttributes<T extends SupportedElement> = Partial<{
|
||||||
|
[K in keyof _fromElsInterfaces<T>]: _fromElsInterfaces<T>[K] extends ((...p: any[]) => any) ? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>) => Signal<ReturnType<_fromElsInterfaces<T>[K]>, {}>) : (IsReadonly<_fromElsInterfaces<T>, K> extends false ? _fromElsInterfaces<T>[K] | Signal<_fromElsInterfaces<T>[K], {}> : ddeStringable);
|
||||||
|
} & AttrsModified> & Record<string, any>;
|
||||||
|
export function classListDeclarative<El extends SupportedElement>(element: El, classList: AttrsModified["classList"]): El;
|
||||||
|
export function assign<El extends SupportedElement>(element: El, ...attrs_array: ElementAttributes<El>[]): El;
|
||||||
|
export function assignAttribute<El extends SupportedElement, ATT extends keyof ElementAttributes<El>>(element: El, attr: ATT, value: ElementAttributes<El>[ATT]): ElementAttributes<El>[ATT];
|
||||||
|
export type ExtendedHTMLElementTagNameMap = HTMLElementTagNameMap & CustomElementTagNameMap;
|
||||||
|
export namespace el {
|
||||||
|
/**
|
||||||
|
* Creates a marker comment for elements
|
||||||
|
*
|
||||||
|
* @param attrs - Marker attributes
|
||||||
|
* @param [is_open=false] - Whether the marker is open-ended
|
||||||
|
* @returns Comment node marker
|
||||||
|
*/
|
||||||
|
export function mark(attrs: {
|
||||||
|
type: "component" | "reactive" | "later";
|
||||||
|
name?: string;
|
||||||
|
host?: "this" | "parentElement";
|
||||||
|
}, is_open?: boolean): Comment;
|
||||||
|
}
|
||||||
|
export function chainableAppend<EL extends SupportedElement>(el: EL): EL | ddeHTMLElement;
|
||||||
|
export function el<A extends ddeComponentAttributes, EL extends SupportedElement | ddeDocumentFragment>(component: (attr: A, ...rest: any[]) => EL, attrs?: NoInfer<A>, ...addons: ddeElementAddon<EL>[]): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? EL : (EL extends ddeDocumentFragment ? EL : ddeHTMLElement);
|
||||||
|
export function el<A extends {
|
||||||
|
textContent: ddeStringable;
|
||||||
|
}, EL extends SupportedElement | ddeDocumentFragment>(component: (attr: A, ...rest: any[]) => EL, attrs?: NoInfer<A>["textContent"], ...addons: ddeElementAddon<EL>[]): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? EL : (EL extends ddeDocumentFragment ? EL : ddeHTMLElement);
|
||||||
|
export function el<TAG extends keyof ExtendedHTMLElementTagNameMap>(tag_name: TAG, attrs?: ElementAttributes<ExtendedHTMLElementTagNameMap[NoInfer<TAG>]> | ddeStringable, ...addons: ddeElementAddon<ExtendedHTMLElementTagNameMap[NoInfer<TAG>]>[]): TAG extends keyof ddeHTMLElementTagNameMap ? ddeHTMLElementTagNameMap[TAG] : ddeHTMLElement;
|
||||||
|
export function el(tag_name?: "<>"): ddeDocumentFragment;
|
||||||
|
export function el(tag_name: string, attrs?: ElementAttributes<HTMLElement> | ddeStringable, ...addons: ddeElementAddon<HTMLElement>[]): ddeHTMLElement;
|
||||||
|
export function elNS(namespace: "http://www.w3.org/2000/svg"): <TAG extends keyof SVGElementTagNameMap & string, EL extends (TAG extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[TAG] : SVGElement)>(tag_name: TAG, attrs?: ElementAttributes<NoInfer<EL>> | ddeStringable, ...addons: ddeElementAddon<NoInfer<EL>>[]) => TAG extends keyof ddeSVGElementTagNameMap ? ddeSVGElementTagNameMap[TAG] : ddeSVGElement;
|
||||||
|
export function elNS(namespace: "http://www.w3.org/1998/Math/MathML"): <TAG extends keyof MathMLElementTagNameMap & string, EL extends (TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement)>(tag_name: TAG, attrs?: ddeStringable | Partial<{
|
||||||
|
[key in keyof EL]: EL[key] | Signal<EL[key], {}> | string | number | boolean;
|
||||||
|
}>, ...addons: ddeElementAddon<NoInfer<EL>>[]) => ddeMathMLElement;
|
||||||
|
export function elNS(namespace: string): (tag_name: string, attrs?: string | ddeStringable | Record<string, any>, ...addons: ddeElementAddon<SupportedElement>[]) => SupportedElement;
|
||||||
|
/** Simulate slots for ddeComponents */
|
||||||
|
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(root: EL): EL;
|
||||||
|
/**
|
||||||
|
* Simulate slots in Custom Elements without using `shadowRoot`.
|
||||||
|
* @param el Custom Element root element
|
||||||
|
* @param body Body of the custom element
|
||||||
|
* */
|
||||||
|
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(el: HTMLElement, body: EL): EL;
|
||||||
|
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, host: Host<SupportedElement>): (data?: any) => void;
|
||||||
|
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?: EventInit): (element: SupportedElement, data?: any) => void;
|
||||||
|
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
|
||||||
|
export interface On {
|
||||||
|
/** Listens to the DOM event. See {@link Document.addEventListener} */
|
||||||
|
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & {
|
||||||
|
target: EL;
|
||||||
|
}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
|
||||||
|
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
|
||||||
|
/** 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>;
|
||||||
|
/** 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>;
|
||||||
|
/**
|
||||||
|
* Fires after the next tick of the Javascript event loop.
|
||||||
|
* This is handy for example to apply some property depending on the element content:
|
||||||
|
* ```js
|
||||||
|
* const selected= "Z";
|
||||||
|
* //...
|
||||||
|
* return el("form").append(
|
||||||
|
* el("select", null, on.defer(e=> e.value=selected)).append(
|
||||||
|
* el("option", { value: "A", textContent: "A" }),
|
||||||
|
* //...
|
||||||
|
* el("option", { value: "Z", textContent: "Z" }),
|
||||||
|
* ),
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>;
|
||||||
|
}
|
||||||
|
export const on: On;
|
||||||
|
export type Scope = {
|
||||||
|
scope: Node | Function | Object;
|
||||||
|
host: Host<SupportedElement>;
|
||||||
|
custom_element: false | HTMLElement;
|
||||||
|
prevent: boolean;
|
||||||
|
};
|
||||||
|
/** Current scope created last time the `el(Function)` was invoke. (Or {@link scope.push}) */
|
||||||
|
export const scope: {
|
||||||
|
current: Scope;
|
||||||
|
/** Stops all automatizations. E. g. signals used as attributes in current scope
|
||||||
|
* registers removing these listeners (and clean signal if no other listeners are detected)
|
||||||
|
* on `disconnected` event. */
|
||||||
|
preventDefault<T extends boolean>(prevent: T): T;
|
||||||
|
/**
|
||||||
|
* This represents reference to the current host element — `scope.host()`.
|
||||||
|
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
||||||
|
* — `scope.host(on.connected(console.log))`.
|
||||||
|
* */
|
||||||
|
host: Host<SupportedElement>;
|
||||||
|
/**
|
||||||
|
* Creates/gets an AbortController that triggers when the element disconnects
|
||||||
|
* */
|
||||||
|
signal: AbortSignal;
|
||||||
|
state: Scope[];
|
||||||
|
/** Adds new child scope. All attributes are inherited by default. */
|
||||||
|
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>;
|
||||||
|
/** Adds root scope as a child of the current scope. */
|
||||||
|
pushRoot(): ReturnType<Array<Scope>["push"]>;
|
||||||
|
/** Removes last/current child scope. */
|
||||||
|
pop(): ReturnType<Array<Scope>["pop"]>;
|
||||||
|
};
|
||||||
|
export function customElementRender<EL extends HTMLElement, P extends any = Record<string, string | Signal<string, {}>>>(target: ShadowRoot | EL, render: (props: P) => SupportedElement | DocumentFragment, props?: P | ((el: EL) => P)): EL;
|
||||||
|
export function customElementWithDDE<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||||
|
export function lifecyclesToEvents<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||||
|
/**
|
||||||
|
* This is used primarly for server side rendering. To be sure that all async operations
|
||||||
|
* are finished before the page is sent to the client.
|
||||||
|
* ```
|
||||||
|
* // on component
|
||||||
|
* function component(){
|
||||||
|
* …
|
||||||
|
* queue(fetch(...).then(...));
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // building the page
|
||||||
|
* async function build(){
|
||||||
|
* const { component }= await import("./component.js");
|
||||||
|
* document.body.append(el(component));
|
||||||
|
* await queue();
|
||||||
|
* retutn document.body.innerHTML;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
export function queue(promise?: Promise<unknown>): Promise<unknown>;
|
||||||
|
/**
|
||||||
|
* Memoization utility for caching DOM elements to improve performance.
|
||||||
|
* Used to prevent unnecessary recreation of elements when rendering lists or complex components.
|
||||||
|
*
|
||||||
|
* @param key - Unique identifier for the element (usually an ID or unique value)
|
||||||
|
* @param generator - Function that creates the element
|
||||||
|
* @returns The cached element if the key exists, otherwise the result of the generator function
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* // Within S.el for list rendering
|
||||||
|
* S.el(itemsSignal, (items, memo) =>
|
||||||
|
* el("ul").append(
|
||||||
|
* ...items.map(item =>
|
||||||
|
* memo(item.id, () => el(ItemComponent, item))
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function memo<T>(key: string | number | object, generator: (key: any) => T): T;
|
||||||
|
/**
|
||||||
|
* Memo namespace containing utility functions for memoization.
|
||||||
|
*/
|
||||||
|
export namespace memo {
|
||||||
|
/**
|
||||||
|
* Checks if an object is a memo scope.
|
||||||
|
* @param obj - The object to check
|
||||||
|
* @returns True if the object is a memo scope
|
||||||
|
*/
|
||||||
|
export function isScope(obj: any): boolean;
|
||||||
|
/**
|
||||||
|
* Creates a memoized function with optional cleanup support.
|
||||||
|
*
|
||||||
|
* @param fun - The function to memoize
|
||||||
|
* @param options - Configuration options
|
||||||
|
* @param options.signal - AbortSignal for cleanup
|
||||||
|
* @param options.onlyLast - When true, only keeps the cache from the most recent call
|
||||||
|
* @returns A memoized version of the function with a .clear() method
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const renderItems = memo.scope(function(items) {
|
||||||
|
* return items.map(item =>
|
||||||
|
* memo(item.id, () => el("div", item.name))
|
||||||
|
* );
|
||||||
|
* }, {
|
||||||
|
* signal: controller.signal,
|
||||||
|
* onlyLast: true
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function scope<F extends Function>(fun: F, options?: {
|
||||||
|
signal?: AbortSignal;
|
||||||
|
onlyLast?: boolean;
|
||||||
|
}): F & {
|
||||||
|
clear: () => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/* TypeScript MEH */
|
||||||
|
declare global {
|
||||||
|
type ddeAppend<el> = (...nodes: (Node | string)[]) => el;
|
||||||
|
interface ddeDocumentFragment extends DocumentFragment {
|
||||||
|
append: ddeAppend<ddeDocumentFragment>;
|
||||||
|
}
|
||||||
|
interface ddeHTMLElement extends HTMLElement {
|
||||||
|
append: ddeAppend<ddeHTMLElement>;
|
||||||
|
}
|
||||||
|
interface ddeSVGElement extends SVGElement {
|
||||||
|
append: ddeAppend<ddeSVGElement>;
|
||||||
|
}
|
||||||
|
interface ddeMathMLElement extends MathMLElement {
|
||||||
|
append: ddeAppend<ddeMathMLElement>;
|
||||||
|
}
|
||||||
|
interface ddeHTMLElementTagNameMap {
|
||||||
|
"a": ddeHTMLAnchorElement;
|
||||||
|
"area": ddeHTMLAreaElement;
|
||||||
|
"audio": ddeHTMLAudioElement;
|
||||||
|
"base": ddeHTMLBaseElement;
|
||||||
|
"blockquote": ddeHTMLQuoteElement;
|
||||||
|
"body": ddeHTMLBodyElement;
|
||||||
|
"br": ddeHTMLBRElement;
|
||||||
|
"button": ddeHTMLButtonElement;
|
||||||
|
"canvas": ddeHTMLCanvasElement;
|
||||||
|
"caption": ddeHTMLTableCaptionElement;
|
||||||
|
"col": ddeHTMLTableColElement;
|
||||||
|
"colgroup": ddeHTMLTableColElement;
|
||||||
|
"data": ddeHTMLDataElement;
|
||||||
|
"datalist": ddeHTMLDataListElement;
|
||||||
|
"del": ddeHTMLModElement;
|
||||||
|
"details": ddeHTMLDetailsElement;
|
||||||
|
"dialog": ddeHTMLDialogElement;
|
||||||
|
"div": ddeHTMLDivElement;
|
||||||
|
"dl": ddeHTMLDListElement;
|
||||||
|
"embed": ddeHTMLEmbedElement;
|
||||||
|
"fieldset": ddeHTMLFieldSetElement;
|
||||||
|
"form": ddeHTMLFormElement;
|
||||||
|
"h1": ddeHTMLHeadingElement;
|
||||||
|
"h2": ddeHTMLHeadingElement;
|
||||||
|
"h3": ddeHTMLHeadingElement;
|
||||||
|
"h4": ddeHTMLHeadingElement;
|
||||||
|
"h5": ddeHTMLHeadingElement;
|
||||||
|
"h6": ddeHTMLHeadingElement;
|
||||||
|
"head": ddeHTMLHeadElement;
|
||||||
|
"hr": ddeHTMLHRElement;
|
||||||
|
"html": ddeHTMLHtmlElement;
|
||||||
|
"iframe": ddeHTMLIFrameElement;
|
||||||
|
"img": ddeHTMLImageElement;
|
||||||
|
"input": ddeHTMLInputElement;
|
||||||
|
"ins": ddeHTMLModElement;
|
||||||
|
"label": ddeHTMLLabelElement;
|
||||||
|
"legend": ddeHTMLLegendElement;
|
||||||
|
"li": ddeHTMLLIElement;
|
||||||
|
"link": ddeHTMLLinkElement;
|
||||||
|
"map": ddeHTMLMapElement;
|
||||||
|
"menu": ddeHTMLMenuElement;
|
||||||
|
"meta": ddeHTMLMetaElement;
|
||||||
|
"meter": ddeHTMLMeterElement;
|
||||||
|
"object": ddeHTMLObjectElement;
|
||||||
|
"ol": ddeHTMLOListElement;
|
||||||
|
"optgroup": ddeHTMLOptGroupElement;
|
||||||
|
"option": ddeHTMLOptionElement;
|
||||||
|
"output": ddeHTMLOutputElement;
|
||||||
|
"p": ddeHTMLParagraphElement;
|
||||||
|
"picture": ddeHTMLPictureElement;
|
||||||
|
"pre": ddeHTMLPreElement;
|
||||||
|
"progress": ddeHTMLProgressElement;
|
||||||
|
"q": ddeHTMLQuoteElement;
|
||||||
|
"script": ddeHTMLScriptElement;
|
||||||
|
"select": ddeHTMLSelectElement;
|
||||||
|
"slot": ddeHTMLSlotElement;
|
||||||
|
"source": ddeHTMLSourceElement;
|
||||||
|
"span": ddeHTMLSpanElement;
|
||||||
|
"style": ddeHTMLStyleElement;
|
||||||
|
"table": ddeHTMLTableElement;
|
||||||
|
"tbody": ddeHTMLTableSectionElement;
|
||||||
|
"td": ddeHTMLTableCellElement;
|
||||||
|
"template": ddeHTMLTemplateElement;
|
||||||
|
"textarea": ddeHTMLTextAreaElement;
|
||||||
|
"tfoot": ddeHTMLTableSectionElement;
|
||||||
|
"th": ddeHTMLTableCellElement;
|
||||||
|
"thead": ddeHTMLTableSectionElement;
|
||||||
|
"time": ddeHTMLTimeElement;
|
||||||
|
"title": ddeHTMLTitleElement;
|
||||||
|
"tr": ddeHTMLTableRowElement;
|
||||||
|
"track": ddeHTMLTrackElement;
|
||||||
|
"ul": ddeHTMLUListElement;
|
||||||
|
"video": ddeHTMLVideoElement;
|
||||||
|
}
|
||||||
|
interface ddeSVGElementTagNameMap {
|
||||||
|
"a": ddeSVGAElement;
|
||||||
|
"animate": ddeSVGAnimateElement;
|
||||||
|
"animateMotion": ddeSVGAnimateMotionElement;
|
||||||
|
"animateTransform": ddeSVGAnimateTransformElement;
|
||||||
|
"circle": ddeSVGCircleElement;
|
||||||
|
"clipPath": ddeSVGClipPathElement;
|
||||||
|
"defs": ddeSVGDefsElement;
|
||||||
|
"desc": ddeSVGDescElement;
|
||||||
|
"ellipse": ddeSVGEllipseElement;
|
||||||
|
"feBlend": ddeSVGFEBlendElement;
|
||||||
|
"feColorMatrix": ddeSVGFEColorMatrixElement;
|
||||||
|
"feComponentTransfer": ddeSVGFEComponentTransferElement;
|
||||||
|
"feComposite": ddeSVGFECompositeElement;
|
||||||
|
"feConvolveMatrix": ddeSVGFEConvolveMatrixElement;
|
||||||
|
"feDiffuseLighting": ddeSVGFEDiffuseLightingElement;
|
||||||
|
"feDisplacementMap": ddeSVGFEDisplacementMapElement;
|
||||||
|
"feDistantLight": ddeSVGFEDistantLightElement;
|
||||||
|
"feDropShadow": ddeSVGFEDropShadowElement;
|
||||||
|
"feFlood": ddeSVGFEFloodElement;
|
||||||
|
"feFuncA": ddeSVGFEFuncAElement;
|
||||||
|
"feFuncB": ddeSVGFEFuncBElement;
|
||||||
|
"feFuncG": ddeSVGFEFuncGElement;
|
||||||
|
"feFuncR": ddeSVGFEFuncRElement;
|
||||||
|
"feGaussianBlur": ddeSVGFEGaussianBlurElement;
|
||||||
|
"feImage": ddeSVGFEImageElement;
|
||||||
|
"feMerge": ddeSVGFEMergeElement;
|
||||||
|
"feMergeNode": ddeSVGFEMergeNodeElement;
|
||||||
|
"feMorphology": ddeSVGFEMorphologyElement;
|
||||||
|
"feOffset": ddeSVGFEOffsetElement;
|
||||||
|
"fePointLight": ddeSVGFEPointLightElement;
|
||||||
|
"feSpecularLighting": ddeSVGFESpecularLightingElement;
|
||||||
|
"feSpotLight": ddeSVGFESpotLightElement;
|
||||||
|
"feTile": ddeSVGFETileElement;
|
||||||
|
"feTurbulence": ddeSVGFETurbulenceElement;
|
||||||
|
"filter": ddeSVGFilterElement;
|
||||||
|
"foreignObject": ddeSVGForeignObjectElement;
|
||||||
|
"g": ddeSVGGElement;
|
||||||
|
"image": ddeSVGImageElement;
|
||||||
|
"line": ddeSVGLineElement;
|
||||||
|
"linearGradient": ddeSVGLinearGradientElement;
|
||||||
|
"marker": ddeSVGMarkerElement;
|
||||||
|
"mask": ddeSVGMaskElement;
|
||||||
|
"metadata": ddeSVGMetadataElement;
|
||||||
|
"mpath": ddeSVGMPathElement;
|
||||||
|
"path": ddeSVGPathElement;
|
||||||
|
"pattern": ddeSVGPatternElement;
|
||||||
|
"polygon": ddeSVGPolygonElement;
|
||||||
|
"polyline": ddeSVGPolylineElement;
|
||||||
|
"radialGradient": ddeSVGRadialGradientElement;
|
||||||
|
"rect": ddeSVGRectElement;
|
||||||
|
"script": ddeSVGScriptElement;
|
||||||
|
"set": ddeSVGSetElement;
|
||||||
|
"stop": ddeSVGStopElement;
|
||||||
|
"style": ddeSVGStyleElement;
|
||||||
|
"svg": ddeSVGSVGElement;
|
||||||
|
"switch": ddeSVGSwitchElement;
|
||||||
|
"symbol": ddeSVGSymbolElement;
|
||||||
|
"text": ddeSVGTextElement;
|
||||||
|
"textPath": ddeSVGTextPathElement;
|
||||||
|
"title": ddeSVGTitleElement;
|
||||||
|
"tspan": ddeSVGTSpanElement;
|
||||||
|
"use": ddeSVGUseElement;
|
||||||
|
"view": ddeSVGViewElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// editorconfig-checker-disable
|
||||||
|
export interface ddeHTMLAnchorElement extends HTMLAnchorElement {
|
||||||
|
append: ddeAppend<ddeHTMLAnchorElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLAreaElement extends HTMLAreaElement {
|
||||||
|
append: ddeAppend<ddeHTMLAreaElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLAudioElement extends HTMLAudioElement {
|
||||||
|
append: ddeAppend<ddeHTMLAudioElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLBaseElement extends HTMLBaseElement {
|
||||||
|
append: ddeAppend<ddeHTMLBaseElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLQuoteElement extends HTMLQuoteElement {
|
||||||
|
append: ddeAppend<ddeHTMLQuoteElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLBodyElement extends HTMLBodyElement {
|
||||||
|
append: ddeAppend<ddeHTMLBodyElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLBRElement extends HTMLBRElement {
|
||||||
|
append: ddeAppend<ddeHTMLBRElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLButtonElement extends HTMLButtonElement {
|
||||||
|
append: ddeAppend<ddeHTMLButtonElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLCanvasElement extends HTMLCanvasElement {
|
||||||
|
append: ddeAppend<ddeHTMLCanvasElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableCaptionElement extends HTMLTableCaptionElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableCaptionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableColElement extends HTMLTableColElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableColElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableColElement extends HTMLTableColElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableColElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDataElement extends HTMLDataElement {
|
||||||
|
append: ddeAppend<ddeHTMLDataElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDataListElement extends HTMLDataListElement {
|
||||||
|
append: ddeAppend<ddeHTMLDataListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLModElement extends HTMLModElement {
|
||||||
|
append: ddeAppend<ddeHTMLModElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDetailsElement extends HTMLDetailsElement {
|
||||||
|
append: ddeAppend<ddeHTMLDetailsElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDialogElement extends HTMLDialogElement {
|
||||||
|
append: ddeAppend<ddeHTMLDialogElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDivElement extends HTMLDivElement {
|
||||||
|
append: ddeAppend<ddeHTMLDivElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLDListElement extends HTMLDListElement {
|
||||||
|
append: ddeAppend<ddeHTMLDListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLEmbedElement extends HTMLEmbedElement {
|
||||||
|
append: ddeAppend<ddeHTMLEmbedElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLFieldSetElement extends HTMLFieldSetElement {
|
||||||
|
append: ddeAppend<ddeHTMLFieldSetElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLFormElement extends HTMLFormElement {
|
||||||
|
append: ddeAppend<ddeHTMLFormElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHeadingElement extends HTMLHeadingElement {
|
||||||
|
append: ddeAppend<ddeHTMLHeadingElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHeadElement extends HTMLHeadElement {
|
||||||
|
append: ddeAppend<ddeHTMLHeadElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHRElement extends HTMLHRElement {
|
||||||
|
append: ddeAppend<ddeHTMLHRElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLHtmlElement extends HTMLHtmlElement {
|
||||||
|
append: ddeAppend<ddeHTMLHtmlElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLIFrameElement extends HTMLIFrameElement {
|
||||||
|
append: ddeAppend<ddeHTMLIFrameElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLImageElement extends HTMLImageElement {
|
||||||
|
append: ddeAppend<ddeHTMLImageElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLInputElement extends HTMLInputElement {
|
||||||
|
append: ddeAppend<ddeHTMLInputElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLabelElement extends HTMLLabelElement {
|
||||||
|
append: ddeAppend<ddeHTMLLabelElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLegendElement extends HTMLLegendElement {
|
||||||
|
append: ddeAppend<ddeHTMLLegendElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLIElement extends HTMLLIElement {
|
||||||
|
append: ddeAppend<ddeHTMLLIElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLLinkElement extends HTMLLinkElement {
|
||||||
|
append: ddeAppend<ddeHTMLLinkElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMapElement extends HTMLMapElement {
|
||||||
|
append: ddeAppend<ddeHTMLMapElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMenuElement extends HTMLMenuElement {
|
||||||
|
append: ddeAppend<ddeHTMLMenuElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMetaElement extends HTMLMetaElement {
|
||||||
|
append: ddeAppend<ddeHTMLMetaElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLMeterElement extends HTMLMeterElement {
|
||||||
|
append: ddeAppend<ddeHTMLMeterElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLObjectElement extends HTMLObjectElement {
|
||||||
|
append: ddeAppend<ddeHTMLObjectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOListElement extends HTMLOListElement {
|
||||||
|
append: ddeAppend<ddeHTMLOListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOptGroupElement extends HTMLOptGroupElement {
|
||||||
|
append: ddeAppend<ddeHTMLOptGroupElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOptionElement extends HTMLOptionElement {
|
||||||
|
append: ddeAppend<ddeHTMLOptionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLOutputElement extends HTMLOutputElement {
|
||||||
|
append: ddeAppend<ddeHTMLOutputElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLParagraphElement extends HTMLParagraphElement {
|
||||||
|
append: ddeAppend<ddeHTMLParagraphElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLPictureElement extends HTMLPictureElement {
|
||||||
|
append: ddeAppend<ddeHTMLPictureElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLPreElement extends HTMLPreElement {
|
||||||
|
append: ddeAppend<ddeHTMLPreElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLProgressElement extends HTMLProgressElement {
|
||||||
|
append: ddeAppend<ddeHTMLProgressElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLScriptElement extends HTMLScriptElement {
|
||||||
|
append: ddeAppend<ddeHTMLScriptElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSelectElement extends HTMLSelectElement {
|
||||||
|
append: ddeAppend<ddeHTMLSelectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSlotElement extends HTMLSlotElement {
|
||||||
|
append: ddeAppend<ddeHTMLSlotElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSourceElement extends HTMLSourceElement {
|
||||||
|
append: ddeAppend<ddeHTMLSourceElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLSpanElement extends HTMLSpanElement {
|
||||||
|
append: ddeAppend<ddeHTMLSpanElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLStyleElement extends HTMLStyleElement {
|
||||||
|
append: ddeAppend<ddeHTMLStyleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableElement extends HTMLTableElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableSectionElement extends HTMLTableSectionElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableSectionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableCellElement extends HTMLTableCellElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableCellElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTemplateElement extends HTMLTemplateElement {
|
||||||
|
append: ddeAppend<ddeHTMLTemplateElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTextAreaElement extends HTMLTextAreaElement {
|
||||||
|
append: ddeAppend<ddeHTMLTextAreaElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableCellElement extends HTMLTableCellElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableCellElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTimeElement extends HTMLTimeElement {
|
||||||
|
append: ddeAppend<ddeHTMLTimeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTitleElement extends HTMLTitleElement {
|
||||||
|
append: ddeAppend<ddeHTMLTitleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTableRowElement extends HTMLTableRowElement {
|
||||||
|
append: ddeAppend<ddeHTMLTableRowElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLTrackElement extends HTMLTrackElement {
|
||||||
|
append: ddeAppend<ddeHTMLTrackElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLUListElement extends HTMLUListElement {
|
||||||
|
append: ddeAppend<ddeHTMLUListElement>;
|
||||||
|
}
|
||||||
|
export interface ddeHTMLVideoElement extends HTMLVideoElement {
|
||||||
|
append: ddeAppend<ddeHTMLVideoElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAElement extends SVGAElement {
|
||||||
|
append: ddeAppend<ddeSVGAElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAnimateElement extends SVGAnimateElement {
|
||||||
|
append: ddeAppend<ddeSVGAnimateElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAnimateMotionElement extends SVGAnimateMotionElement {
|
||||||
|
append: ddeAppend<ddeSVGAnimateMotionElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGAnimateTransformElement extends SVGAnimateTransformElement {
|
||||||
|
append: ddeAppend<ddeSVGAnimateTransformElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGCircleElement extends SVGCircleElement {
|
||||||
|
append: ddeAppend<ddeSVGCircleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGClipPathElement extends SVGClipPathElement {
|
||||||
|
append: ddeAppend<ddeSVGClipPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGDefsElement extends SVGDefsElement {
|
||||||
|
append: ddeAppend<ddeSVGDefsElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGDescElement extends SVGDescElement {
|
||||||
|
append: ddeAppend<ddeSVGDescElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGEllipseElement extends SVGEllipseElement {
|
||||||
|
append: ddeAppend<ddeSVGEllipseElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEBlendElement extends SVGFEBlendElement {
|
||||||
|
append: ddeAppend<ddeSVGFEBlendElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEColorMatrixElement extends SVGFEColorMatrixElement {
|
||||||
|
append: ddeAppend<ddeSVGFEColorMatrixElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEComponentTransferElement extends SVGFEComponentTransferElement {
|
||||||
|
append: ddeAppend<ddeSVGFEComponentTransferElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFECompositeElement extends SVGFECompositeElement {
|
||||||
|
append: ddeAppend<ddeSVGFECompositeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEConvolveMatrixElement extends SVGFEConvolveMatrixElement {
|
||||||
|
append: ddeAppend<ddeSVGFEConvolveMatrixElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDiffuseLightingElement extends SVGFEDiffuseLightingElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDiffuseLightingElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDisplacementMapElement extends SVGFEDisplacementMapElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDisplacementMapElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDistantLightElement extends SVGFEDistantLightElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDistantLightElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEDropShadowElement extends SVGFEDropShadowElement {
|
||||||
|
append: ddeAppend<ddeSVGFEDropShadowElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFloodElement extends SVGFEFloodElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFloodElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncAElement extends SVGFEFuncAElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncAElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncBElement extends SVGFEFuncBElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncBElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncGElement extends SVGFEFuncGElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncGElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEFuncRElement extends SVGFEFuncRElement {
|
||||||
|
append: ddeAppend<ddeSVGFEFuncRElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEGaussianBlurElement extends SVGFEGaussianBlurElement {
|
||||||
|
append: ddeAppend<ddeSVGFEGaussianBlurElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEImageElement extends SVGFEImageElement {
|
||||||
|
append: ddeAppend<ddeSVGFEImageElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEMergeElement extends SVGFEMergeElement {
|
||||||
|
append: ddeAppend<ddeSVGFEMergeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEMergeNodeElement extends SVGFEMergeNodeElement {
|
||||||
|
append: ddeAppend<ddeSVGFEMergeNodeElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEMorphologyElement extends SVGFEMorphologyElement {
|
||||||
|
append: ddeAppend<ddeSVGFEMorphologyElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEOffsetElement extends SVGFEOffsetElement {
|
||||||
|
append: ddeAppend<ddeSVGFEOffsetElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFEPointLightElement extends SVGFEPointLightElement {
|
||||||
|
append: ddeAppend<ddeSVGFEPointLightElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFESpecularLightingElement extends SVGFESpecularLightingElement {
|
||||||
|
append: ddeAppend<ddeSVGFESpecularLightingElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFESpotLightElement extends SVGFESpotLightElement {
|
||||||
|
append: ddeAppend<ddeSVGFESpotLightElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFETileElement extends SVGFETileElement {
|
||||||
|
append: ddeAppend<ddeSVGFETileElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFETurbulenceElement extends SVGFETurbulenceElement {
|
||||||
|
append: ddeAppend<ddeSVGFETurbulenceElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGFilterElement extends SVGFilterElement {
|
||||||
|
append: ddeAppend<ddeSVGFilterElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGForeignObjectElement extends SVGForeignObjectElement {
|
||||||
|
append: ddeAppend<ddeSVGForeignObjectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGGElement extends SVGGElement {
|
||||||
|
append: ddeAppend<ddeSVGGElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGImageElement extends SVGImageElement {
|
||||||
|
append: ddeAppend<ddeSVGImageElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGLineElement extends SVGLineElement {
|
||||||
|
append: ddeAppend<ddeSVGLineElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGLinearGradientElement extends SVGLinearGradientElement {
|
||||||
|
append: ddeAppend<ddeSVGLinearGradientElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMarkerElement extends SVGMarkerElement {
|
||||||
|
append: ddeAppend<ddeSVGMarkerElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMaskElement extends SVGMaskElement {
|
||||||
|
append: ddeAppend<ddeSVGMaskElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMetadataElement extends SVGMetadataElement {
|
||||||
|
append: ddeAppend<ddeSVGMetadataElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGMPathElement extends SVGMPathElement {
|
||||||
|
append: ddeAppend<ddeSVGMPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPathElement extends SVGPathElement {
|
||||||
|
append: ddeAppend<ddeSVGPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPatternElement extends SVGPatternElement {
|
||||||
|
append: ddeAppend<ddeSVGPatternElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPolygonElement extends SVGPolygonElement {
|
||||||
|
append: ddeAppend<ddeSVGPolygonElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGPolylineElement extends SVGPolylineElement {
|
||||||
|
append: ddeAppend<ddeSVGPolylineElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGRadialGradientElement extends SVGRadialGradientElement {
|
||||||
|
append: ddeAppend<ddeSVGRadialGradientElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGRectElement extends SVGRectElement {
|
||||||
|
append: ddeAppend<ddeSVGRectElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGScriptElement extends SVGScriptElement {
|
||||||
|
append: ddeAppend<ddeSVGScriptElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSetElement extends SVGSetElement {
|
||||||
|
append: ddeAppend<ddeSVGSetElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGStopElement extends SVGStopElement {
|
||||||
|
append: ddeAppend<ddeSVGStopElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGStyleElement extends SVGStyleElement {
|
||||||
|
append: ddeAppend<ddeSVGStyleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSVGElement extends SVGSVGElement {
|
||||||
|
append: ddeAppend<ddeSVGSVGElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSwitchElement extends SVGSwitchElement {
|
||||||
|
append: ddeAppend<ddeSVGSwitchElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGSymbolElement extends SVGSymbolElement {
|
||||||
|
append: ddeAppend<ddeSVGSymbolElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTextElement extends SVGTextElement {
|
||||||
|
append: ddeAppend<ddeSVGTextElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTextPathElement extends SVGTextPathElement {
|
||||||
|
append: ddeAppend<ddeSVGTextPathElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTitleElement extends SVGTitleElement {
|
||||||
|
append: ddeAppend<ddeSVGTitleElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGTSpanElement extends SVGTSpanElement {
|
||||||
|
append: ddeAppend<ddeSVGTSpanElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGUseElement extends SVGUseElement {
|
||||||
|
append: ddeAppend<ddeSVGUseElement>;
|
||||||
|
}
|
||||||
|
export interface ddeSVGViewElement extends SVGViewElement {
|
||||||
|
append: ddeAppend<ddeSVGViewElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
dispatchEvent$1 as dispatchEvent,
|
||||||
|
el as createElement,
|
||||||
|
elNS as createElementNS,
|
||||||
|
};
|
||||||
|
|
||||||
|
export as namespace iife;
|
||||||
|
|
||||||
|
export {};
|
1
dist/iife.min.js
vendored
Normal file
1
dist/iife.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
docs/assets/devtools.png
Normal file
BIN
docs/assets/devtools.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 145 KiB |
@ -6,8 +6,29 @@
|
|||||||
version="1.1"
|
version="1.1"
|
||||||
id="svg5"
|
id="svg5"
|
||||||
xml:space="preserve"
|
xml:space="preserve"
|
||||||
|
sodipodi:docname="logo.svg"
|
||||||
|
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:zoom="3.296875"
|
||||||
|
inkscape:cx="128"
|
||||||
|
inkscape:cy="101.61137"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1052"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="g2" /><defs
|
||||||
id="defs4"><linearGradient
|
id="defs4"><linearGradient
|
||||||
id="bgGradient"
|
id="bgGradient"
|
||||||
x1="18"
|
x1="18"
|
||||||
@ -45,14 +66,14 @@
|
|||||||
style="fill:url(#bgGradient);stroke-width:1.13636"
|
style="fill:url(#bgGradient);stroke-width:1.13636"
|
||||||
ry="50" /><g
|
ry="50" /><g
|
||||||
id="g2"
|
id="g2"
|
||||||
transform="translate(-3.5569814,-1.5165883)"><g
|
transform="translate(0.4430186,-1.5165883)"><g
|
||||||
id="g1"
|
id="g1"
|
||||||
transform="matrix(1.5900346,0,0,1.5900346,-121.12651,-66.626074)"><g
|
transform="matrix(1.5900346,0,0,1.5900346,-121.12651,-66.626074)"><g
|
||||||
opacity="0.25"
|
opacity="0.25"
|
||||||
fill="#ffffff"
|
fill="#ffffff"
|
||||||
filter="url(#glow)"
|
filter="url(#glow)"
|
||||||
id="g5"
|
id="g5"
|
||||||
transform="matrix(0.55415879,0,0,0.56134669,110.90661,51.505106)"><path
|
transform="matrix(0.55415879,0,0,0.56134669,112.16444,51.505106)"><path
|
||||||
d="m 80,60 -30,68 30,68"
|
d="m 80,60 -30,68 30,68"
|
||||||
stroke="#ffffff"
|
stroke="#ffffff"
|
||||||
stroke-width="8"
|
stroke-width="8"
|
||||||
@ -66,6 +87,6 @@
|
|||||||
d="m 152.28246,124.58038 q 0,-4.06854 1.24681,-7.15275 1.24681,-3.08421 3.28107,-5.15785 2.03427,-2.06051 4.65913,-3.11046 2.62486,-1.04994 5.38096,-1.04994 6.82464,0 10.3682,4.02916 3.54356,4.04228 3.54356,11.78562 0,0.78746 -0.0263,1.64054 -0.0394,0.85308 -0.10499,1.37805 h -20.01456 q 0,3.01859 2.49362,4.76412 2.49362,1.73241 6.43091,1.73241 2.42799,0 4.63287,-0.52497 2.19176,-0.52497 3.70106,-1.04995 l 1.11556,6.89026 q -2.09989,0.72184 -4.46226,1.20744 -2.36237,0.49872 -5.31534,0.49872 -3.93729,0 -7.04775,-1.02369 -3.12359,-1.01058 -5.31534,-3.00547 -2.20489,-2.00802 -3.38607,-4.96098 -1.18114,-2.95297 -1.18114,-6.89026 m 20.67077,-3.21546 q 0,-1.2468 -0.3281,-2.40174 -0.32811,-1.14182 -1.04995,-2.06052 -0.72183,-0.9187 -1.8374,-1.48304 -1.11557,-0.55123 -2.7561,-0.55123 -1.57492,0 -2.71673,0.52498 -1.15494,0.52497 -1.91615,1.44367 -0.74809,0.9187 -1.16806,2.09989 -0.43311,1.18119 -0.56435,2.42799 z m 40.75096,17.06159 q -0.19687,0.13125 -0.89245,0.45936 -0.68247,0.3281 -1.79803,0.69558 -1.11557,0.35436 -2.71673,0.61685 -1.61429,0.26248 -3.64856,0.26248 -5.57783,0 -8.13707,-3.32045 -2.55923,-3.30732 -2.55923,-9.67261 v -26.18298 h -8.5308 v -6.693389 h 16.60224 v 33.466969 q 0,3.14983 1.24681,4.26539 1.24681,1.11557 3.14983,1.11557 2.428,0 4.06853,-0.65621 1.64054,-0.65622 2.16551,-0.85308 z"
|
d="m 152.28246,124.58038 q 0,-4.06854 1.24681,-7.15275 1.24681,-3.08421 3.28107,-5.15785 2.03427,-2.06051 4.65913,-3.11046 2.62486,-1.04994 5.38096,-1.04994 6.82464,0 10.3682,4.02916 3.54356,4.04228 3.54356,11.78562 0,0.78746 -0.0263,1.64054 -0.0394,0.85308 -0.10499,1.37805 h -20.01456 q 0,3.01859 2.49362,4.76412 2.49362,1.73241 6.43091,1.73241 2.42799,0 4.63287,-0.52497 2.19176,-0.52497 3.70106,-1.04995 l 1.11556,6.89026 q -2.09989,0.72184 -4.46226,1.20744 -2.36237,0.49872 -5.31534,0.49872 -3.93729,0 -7.04775,-1.02369 -3.12359,-1.01058 -5.31534,-3.00547 -2.20489,-2.00802 -3.38607,-4.96098 -1.18114,-2.95297 -1.18114,-6.89026 m 20.67077,-3.21546 q 0,-1.2468 -0.3281,-2.40174 -0.32811,-1.14182 -1.04995,-2.06052 -0.72183,-0.9187 -1.8374,-1.48304 -1.11557,-0.55123 -2.7561,-0.55123 -1.57492,0 -2.71673,0.52498 -1.15494,0.52497 -1.91615,1.44367 -0.74809,0.9187 -1.16806,2.09989 -0.43311,1.18119 -0.56435,2.42799 z m 40.75096,17.06159 q -0.19687,0.13125 -0.89245,0.45936 -0.68247,0.3281 -1.79803,0.69558 -1.11557,0.35436 -2.71673,0.61685 -1.61429,0.26248 -3.64856,0.26248 -5.57783,0 -8.13707,-3.32045 -2.55923,-3.30732 -2.55923,-9.67261 v -26.18298 h -8.5308 v -6.693389 h 16.60224 v 33.466969 q 0,3.14983 1.24681,4.26539 1.24681,1.11557 3.14983,1.11557 2.428,0 4.06853,-0.65621 1.64054,-0.65622 2.16551,-0.85308 z"
|
||||||
id="path1-3"
|
id="path1-3"
|
||||||
style="fill:#ff5252;stroke-width:1.31243" /></g><path
|
style="fill:#ff5252;stroke-width:1.31243" /></g><path
|
||||||
d="m 27.256467,130.12148 q 0,6.64555 2.489444,10.86495 2.468347,4.21939 7.953563,4.21939 1.582274,0 2.953578,-0.10548 1.371303,-0.10549 2.848092,-0.31646 v -27.00413 q -1.476789,-0.84388 -3.375517,-1.4346 -1.898728,-0.56962 -4.008427,-0.56962 -4.641336,0 -6.751034,3.69197 -2.109699,3.69198 -2.109699,10.65398 m 29.219322,23.62862 q -3.586487,1.16034 -8.755248,1.89873 -5.168761,0.7384 -10.126552,0.7384 -11.603341,0 -17.55269,-6.85651 -5.970447,-6.85652 -5.970447,-18.77632 0,-12.13076 5.021083,-19.15606 4.999985,-7.0042 14.810082,-7.0042 2.637123,0 5.168761,0.56962 2.531638,0.59072 4.430366,1.64557 V 84.235539 l 12.974645,-2.215183 z m 23.523136,-23.62862 q 0,6.64555 2.489444,10.86495 2.468344,4.21939 7.953559,4.21939 1.582268,0 2.953578,-0.10548 1.3713,-0.10549 2.84809,-0.31646 v -27.00413 q -1.47679,-0.84388 -3.37552,-1.4346 -1.89873,-0.56962 -4.008423,-0.56962 -4.64133,0 -6.75103,3.69197 -2.109698,3.69198 -2.109698,10.65398 m 29.219315,23.62862 q -3.58648,1.16034 -8.75524,1.89873 -5.168774,0.7384 -10.126562,0.7384 -11.603332,0 -17.552681,-6.85651 -5.970446,-6.85652 -5.970446,-18.77632 0,-12.13076 4.999984,-19.15606 5.021082,-7.0042 14.831178,-7.0042 2.63712,0 5.168753,0.56962 2.53164,0.59072 4.43037,1.64557 V 84.235539 l 12.974644,-2.215183 z"
|
d="m 25.256467,130.12148 q 0,6.64555 2.489444,10.86495 2.468347,4.21939 7.953563,4.21939 1.582274,0 2.953578,-0.10548 1.371303,-0.10549 2.848092,-0.31646 v -27.00413 q -1.476789,-0.84388 -3.375517,-1.4346 -1.898728,-0.56962 -4.008427,-0.56962 -4.641336,0 -6.751034,3.69197 -2.109699,3.69198 -2.109699,10.65398 m 29.219322,23.62862 q -3.586487,1.16034 -8.755248,1.89873 -5.168761,0.7384 -10.126552,0.7384 -11.603341,0 -17.55269,-6.85651 -5.970447,-6.85652 -5.970447,-18.77632 0,-12.13076 5.021083,-19.15606 4.999985,-7.0042 14.810082,-7.0042 2.637123,0 5.168761,0.56962 2.531638,0.59072 4.430366,1.64557 V 84.235539 l 12.974645,-2.215183 z m 23.523136,-23.62862 q 0,6.64555 2.489444,10.86495 2.468344,4.21939 7.953559,4.21939 1.582268,0 2.953578,-0.10548 1.3713,-0.10549 2.84809,-0.31646 v -27.00413 q -1.47679,-0.84388 -3.37552,-1.4346 -1.89873,-0.56962 -4.008423,-0.56962 -4.64133,0 -6.75103,3.69197 -2.109698,3.69198 -2.109698,10.65398 m 29.219315,23.62862 q -3.58648,1.16034 -8.75524,1.89873 -5.168774,0.7384 -10.126562,0.7384 -11.603332,0 -17.552681,-6.85651 -5.970446,-6.85652 -5.970446,-18.77632 0,-12.13076 4.999984,-19.15606 5.021082,-7.0042 14.831178,-7.0042 2.63712,0 5.168753,0.56962 2.53164,0.59072 4.43037,1.64557 V 84.235539 l 12.974644,-2.215183 z"
|
||||||
id="path1"
|
id="path1"
|
||||||
style="fill:#ff5252;stroke-width:2.1097;fill-opacity:0.66479665" /></g></svg>
|
style="fill:#ff5252;fill-opacity:0.664797;stroke-width:2.1097" /></g></svg>
|
||||||
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 5.3 KiB |
@ -1,4 +1,4 @@
|
|||||||
import { registerClientFile, styles } from "../ssr.js";
|
import { page_id, registerClientFile, styles } from "../ssr.js";
|
||||||
const host= "."+code.name;
|
const host= "."+code.name;
|
||||||
styles.css`
|
styles.css`
|
||||||
/* Code block styling */
|
/* Code block styling */
|
||||||
@ -177,6 +177,9 @@ ${host}:hover .copy-button {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
import { el } from "deka-dom-el";
|
import { el } from "deka-dom-el";
|
||||||
|
/**
|
||||||
|
* @typedef {"js"|"ts"|"html"|"css"|"shell"|"-"} Language
|
||||||
|
* */
|
||||||
/**
|
/**
|
||||||
* Prints code to the page and registers flems to make it interactive.
|
* Prints code to the page and registers flems to make it interactive.
|
||||||
* @param {object} attrs
|
* @param {object} attrs
|
||||||
@ -184,56 +187,35 @@ import { el } from "deka-dom-el";
|
|||||||
* @param {string} [attrs.className]
|
* @param {string} [attrs.className]
|
||||||
* @param {URL} [attrs.src] Example code file path
|
* @param {URL} [attrs.src] Example code file path
|
||||||
* @param {string} [attrs.content] Example code
|
* @param {string} [attrs.content] Example code
|
||||||
* @param {"js"|"ts"|"html"|"css"} [attrs.language="js"] Language of the code
|
* @param {Language} [attrs.language="-s"] Language of the code
|
||||||
* @param {string} [attrs.page_id] ID of the page, if setted it registers shiki
|
|
||||||
* */
|
* */
|
||||||
export function code({ id, src, content, language= "js", className= host.slice(1), page_id }){
|
export function code({ id, src, content, language= "-", className= host.slice(1) }){
|
||||||
if(src) content= s.cat(src);
|
if(src){
|
||||||
|
content= s.cat(src);
|
||||||
|
if(language=== "-") language= /** @type {Language} */(src.pathname.split(".").pop());
|
||||||
|
}
|
||||||
|
content= normalizeIndentation(content);
|
||||||
let dataJS;
|
let dataJS;
|
||||||
if(page_id){
|
if(language!== "-"){
|
||||||
registerClientPart(page_id);
|
registerClientPart();
|
||||||
dataJS= "todo";
|
dataJS= "todo";
|
||||||
}
|
}
|
||||||
return el("div", { id, className, dataJS, tabIndex: 0 }).append(
|
return el("div", { id, className, dataJS, tabIndex: 0 }).append(
|
||||||
el("code", { className: "language-"+language, textContent: content.trim() })
|
el("code", { className: "language-"+language, textContent: content.trim() })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
export function pre({ content }){
|
||||||
|
content= normalizeIndentation(content);
|
||||||
|
return el("pre").append(el("code", content.trim()));
|
||||||
|
}
|
||||||
let is_registered= {};
|
let is_registered= {};
|
||||||
/** @param {string} page_id */
|
function registerClientPart(){
|
||||||
function registerClientPart(page_id){
|
|
||||||
if(is_registered[page_id]) return;
|
if(is_registered[page_id]) return;
|
||||||
|
|
||||||
// Add Shiki with a more reliable loading method
|
// Add Shiki with a more reliable loading method
|
||||||
document.head.append(
|
document.head.append(
|
||||||
// Use a newer version of Shiki with better performance
|
// Use a newer version of Shiki with better performance
|
||||||
el("script", { src: "https://cdn.jsdelivr.net/npm/shiki@0.14.3/dist/index.unpkg.iife.js", defer: true }),
|
el("script", { src: "https://cdn.jsdelivr.net/npm/shiki@0.14.3/dist/index.unpkg.iife.js", defer: true }),
|
||||||
// Make sure we can match Flems styling in dark/light mode
|
|
||||||
el("style", `
|
|
||||||
/* Ensure CodeMirror and Shiki use the same font */
|
|
||||||
.CodeMirror *, .shiki * {
|
|
||||||
font-family: var(--font-mono) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Style Shiki's output to match our theme */
|
|
||||||
.shiki {
|
|
||||||
background-color: var(--shiki-color-background) !important;
|
|
||||||
color: var(--shiki-color-text) !important;
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
tab-size: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure Shiki code tokens use our CSS variables */
|
|
||||||
.shiki .keyword { color: var(--shiki-token-keyword) !important; }
|
|
||||||
.shiki .constant { color: var(--shiki-token-constant) !important; }
|
|
||||||
.shiki .string { color: var(--shiki-token-string) !important; }
|
|
||||||
.shiki .comment { color: var(--shiki-token-comment) !important; }
|
|
||||||
.shiki .function { color: var(--shiki-token-function) !important; }
|
|
||||||
.shiki .operator, .shiki .punctuation { color: var(--shiki-token-punctuation) !important; }
|
|
||||||
.shiki .parameter { color: var(--shiki-token-parameter) !important; }
|
|
||||||
.shiki .variable { color: var(--shiki-token-variable) !important; }
|
|
||||||
.shiki .property { color: var(--shiki-token-property) !important; }
|
|
||||||
`),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
registerClientFile(
|
registerClientFile(
|
||||||
@ -245,3 +227,9 @@ function registerClientPart(page_id){
|
|||||||
|
|
||||||
is_registered[page_id]= true;
|
is_registered[page_id]= true;
|
||||||
}
|
}
|
||||||
|
/** @param {string} src */
|
||||||
|
function normalizeIndentation(src){
|
||||||
|
const lines= src.split("\n");
|
||||||
|
const min_indent= Math.min(...lines.map(line=> line.search(/\S/)).filter(i=> i >= 0));
|
||||||
|
return lines.map(line=> line.slice(min_indent)).join("\n");
|
||||||
|
}
|
||||||
|
175
docs/components/converter.html.js
Normal file
175
docs/components/converter.html.js
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
import { page_id, styles } from "../ssr.js";
|
||||||
|
|
||||||
|
styles.css`
|
||||||
|
#html-to-dde-converter {
|
||||||
|
grid-column: full-main;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background-color: var(--bg-sidebar);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
#html-to-dde-converter h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
#html-to-dde-converter .description {
|
||||||
|
color: var(--text-light);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
margin-top: -1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#html-to-dde-converter .converter-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#html-to-dde-converter .input-group,
|
||||||
|
#html-to-dde-converter .output-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
#html-to-dde-converter [type="number"]{
|
||||||
|
width: 3em;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#html-to-dde-converter label {
|
||||||
|
font-weight: 500;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
#html-to-dde-converter .options {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#html-to-dde-converter .option-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#html-to-dde-converter textarea {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background-color: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
min-height: 200px;
|
||||||
|
height: 25em;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
#html-to-dde-converter textarea:focus {
|
||||||
|
outline: 2px solid var(--primary-light);
|
||||||
|
outline-offset: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#html-to-dde-converter .button-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#html-to-dde-converter button {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: none;
|
||||||
|
background-color: var(--primary);
|
||||||
|
color: var(--button-text);
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#html-to-dde-converter button:hover {
|
||||||
|
background-color: var(--primary-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
#html-to-dde-converter button.secondary {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
#html-to-dde-converter button.secondary:hover {
|
||||||
|
background-color: var(--bg);
|
||||||
|
border-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
#html-to-dde-converter .copy-button {
|
||||||
|
background-color: var(--secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
#html-to-dde-converter .copy-button:hover {
|
||||||
|
background-color: var(--secondary-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
#html-to-dde-converter .status {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
#html-to-dde-converter .error {
|
||||||
|
color: hsl(0, 100%, 60%);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sample HTML examples list */
|
||||||
|
#html-to-dde-converter .examples-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#html-to-dde-converter .example-button {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
import { ireland } from "./ireland.html.js";
|
||||||
|
import { el } from "deka-dom-el";
|
||||||
|
const fileURL= url=> new URL(url, import.meta.url);
|
||||||
|
|
||||||
|
export function converter(){
|
||||||
|
registerClientPart(page_id);
|
||||||
|
return el(ireland, {
|
||||||
|
src: fileURL("./converter.js.js"),
|
||||||
|
exportName: "converter",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_registered= {};
|
||||||
|
/** @param {string} page_id */
|
||||||
|
function registerClientPart(page_id){
|
||||||
|
if(is_registered[page_id]) return;
|
||||||
|
|
||||||
|
document.head.append(
|
||||||
|
el("script", {
|
||||||
|
// src: "https://unpkg.com/@beforesemicolon/html-parser/dist/client.js",
|
||||||
|
src: "https://cdn.jsdelivr.net/npm/@beforesemicolon/html-parser/dist/client.js",
|
||||||
|
type: "text/javascript",
|
||||||
|
charset: "utf-8",
|
||||||
|
defer: true
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
is_registered[page_id]= true;
|
||||||
|
}
|
384
docs/components/converter.js.js
Normal file
384
docs/components/converter.js.js
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
import { el, on } from "deka-dom-el";
|
||||||
|
import { S } from "deka-dom-el/signals";
|
||||||
|
const { parse }= globalThis.BFS || { parse(){ return { children: [ "not implemented" ] } } };
|
||||||
|
// Example HTML snippets
|
||||||
|
const examples = [
|
||||||
|
{
|
||||||
|
name: "Simple Component",
|
||||||
|
html: `<div class="card">
|
||||||
|
<img src="image.jpg" alt="Card Image" class="card-image">
|
||||||
|
<h2 class="card-title">Card Title</h2>
|
||||||
|
<p class="card-text">This is a simple card component</p>
|
||||||
|
<button aria-pressed="mixed" type="button" class="card-button">Click Me</button>
|
||||||
|
</div>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Navigation",
|
||||||
|
html: `<nav class="main-nav">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/" class="active">Home</a></li>
|
||||||
|
<li><a href="/about">About</a></li>
|
||||||
|
<li><a href="/services">Services</a></li>
|
||||||
|
<li><a href="/contact">Contact</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Form",
|
||||||
|
html: `<form class="contact-form" onsubmit="submitForm(event)">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Name:</label>
|
||||||
|
<input type="text" id="name" name="name" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">Email:</label>
|
||||||
|
<input type="email" id="email" name="email" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="message">Message:</label>
|
||||||
|
<textarea id="message" name="message" rows="4" required></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="submit-btn">Send Message</button>
|
||||||
|
</form>`
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Convert HTML to dd<el> code
|
||||||
|
function convertHTMLtoDDE(html, options = {}) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = parse(html);
|
||||||
|
const content = parsed.children[0] || parsed.childNodes[0];
|
||||||
|
return !content ? "" : nodeToDDE(content, options);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Parsing error:", error);
|
||||||
|
return `// Error parsing HTML: ${error.message}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Node types based on standard DOM nodeType values
|
||||||
|
const NODE_TYPE = {
|
||||||
|
ELEMENT: 1, // Standard element node (equivalent to node.type === "element")
|
||||||
|
TEXT: 3, // Text node (equivalent to node.type === "text")
|
||||||
|
COMMENT: 8 // Comment node (equivalent to node.type === "comment")
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert a parsed node to dd<el> code
|
||||||
|
function nodeToDDE(node, options = {}, level = 0) {
|
||||||
|
const tab= options.indent === "-1" ? "\t" : " ".repeat(options.indent);
|
||||||
|
const indent = tab.repeat(level);
|
||||||
|
const nextIndent = tab.repeat(level + 1);
|
||||||
|
|
||||||
|
const { nodeType } = node;
|
||||||
|
// Handle text nodes
|
||||||
|
if (nodeType === NODE_TYPE.TEXT) {
|
||||||
|
const text = el("i", { innerText: node.nodeValue }).textContent;
|
||||||
|
if (!text.trim()) return null;
|
||||||
|
|
||||||
|
// Return as plain text or template string for longer text
|
||||||
|
return text.includes("\n") || text.includes('"')
|
||||||
|
? `\`${text}\``
|
||||||
|
: `"${text}"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle comment nodes
|
||||||
|
if (nodeType === NODE_TYPE.COMMENT) {
|
||||||
|
const text = node.nodeValue;
|
||||||
|
if (!text.trim()) return null;
|
||||||
|
return text.includes("\n")
|
||||||
|
? [ "/*", ...text.trim().split("\n").map(l=> tab+l), "*/" ]
|
||||||
|
: [ `// ${text}` ];
|
||||||
|
}
|
||||||
|
|
||||||
|
// For element nodes
|
||||||
|
if (nodeType === NODE_TYPE.ELEMENT) {
|
||||||
|
// Special case for SVG elements
|
||||||
|
const isNS = node.tagName === "svg";
|
||||||
|
const elFunction = isNS ? "elNS" : "el";
|
||||||
|
|
||||||
|
// Get tag name
|
||||||
|
let tagStr = `"${node.tagName}"`;
|
||||||
|
|
||||||
|
// Process attributes
|
||||||
|
const attrs = [];
|
||||||
|
const sets = {
|
||||||
|
aria: {},
|
||||||
|
data: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const { name: key, value } of node.attributes) {
|
||||||
|
// Handle class attribute
|
||||||
|
if (key === "class") {
|
||||||
|
attrs.push(`className: "${value}"`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle style attribute
|
||||||
|
if (key === "style") {
|
||||||
|
if (options.styleAsObject) {
|
||||||
|
// Convert inline style to object
|
||||||
|
const styleObj = {};
|
||||||
|
value.split(";").forEach(part => {
|
||||||
|
const [propRaw, valueRaw] = part.split(":");
|
||||||
|
if (propRaw && valueRaw) {
|
||||||
|
const prop = propRaw.trim();
|
||||||
|
const propCamel = prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
||||||
|
styleObj[propCamel] = valueRaw.trim();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Object.keys(styleObj).length > 0) {
|
||||||
|
const styleStr = JSON.stringify(styleObj).replace(/"([^"]+)":/g, "$1:");
|
||||||
|
attrs.push(`style: ${styleStr}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Keep as string
|
||||||
|
attrs.push(`style: "${value}"`);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle boolean attributes
|
||||||
|
if (value === "" || value === key) {
|
||||||
|
attrs.push(`${key}: true`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle data/aria attributes
|
||||||
|
if (key.startsWith("data-") || key.startsWith("aria-")) {
|
||||||
|
const keyName = key.startsWith("aria-") ? "aria" : "data";
|
||||||
|
const keyCamel = key.slice(keyName.length + 1).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
||||||
|
sets[keyName][keyCamel] = value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular attributes
|
||||||
|
const keyRegular = key==="for"
|
||||||
|
? "htmlFor"
|
||||||
|
: key.startsWith("on")
|
||||||
|
? `"=${key}"`
|
||||||
|
: key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
||||||
|
attrs.push(`${keyRegular}: "${value}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process sets
|
||||||
|
for (const [name, set] of Object.entries(sets)) {
|
||||||
|
if(options.dataAttrsAsCamel)
|
||||||
|
for (const [key, value] of Object.entries(set))
|
||||||
|
attrs.push(`${name}${key[0].toUpperCase() + key.substring(1)}: "${value}"`);
|
||||||
|
else {
|
||||||
|
const setStr= Object.entries(set).map(([key, value]) => `${key}: "${value}"`).join(",");
|
||||||
|
if (setStr !== "")
|
||||||
|
attrs.push(`${name}set: { ${setStr} }`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process children
|
||||||
|
const children = [];
|
||||||
|
for (const child of node.childNodes) {
|
||||||
|
const childCode = nodeToDDE(child, options, level + 1);
|
||||||
|
if (!childCode) continue;
|
||||||
|
|
||||||
|
children.push(childCode);
|
||||||
|
}
|
||||||
|
if(node.childNodes.length===1 && node.childNodes[0].nodeType===NODE_TYPE.TEXT){
|
||||||
|
const textContent= children.pop().slice(1, -1);
|
||||||
|
attrs.unshift(`textContent: "${textContent}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the element creation code
|
||||||
|
let result = `${elFunction}("${node.tagName.toLowerCase()}"`;
|
||||||
|
|
||||||
|
// Add attributes if any
|
||||||
|
if (attrs.length > 0) {
|
||||||
|
const tooLong= attrs.join(``).length+result.length > 55;
|
||||||
|
if(options.expaned || tooLong || attrs.length > 3)
|
||||||
|
result += `, {\n${nextIndent}${attrs.join(`,\n${nextIndent}`)},\n${indent}}`;
|
||||||
|
else
|
||||||
|
result += `, { ${attrs.join(", ")} }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add children if any
|
||||||
|
if (children.length > 0) {
|
||||||
|
const chs= children.map(ch=>
|
||||||
|
Array.isArray(ch) ? ch.map(l=> nextIndent + l).join("\n") :
|
||||||
|
nextIndent + ch + ",");
|
||||||
|
result += `).append(\n${chs.join("\n")}\n${indent})`;
|
||||||
|
} else {
|
||||||
|
result += ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function converter() {
|
||||||
|
// State for the converter
|
||||||
|
const htmlInput = S(examples[0].html);
|
||||||
|
const error = S("");
|
||||||
|
|
||||||
|
const status = S("");
|
||||||
|
const showStatus= msg => {
|
||||||
|
status.set(msg);
|
||||||
|
// Clear status after 3 seconds
|
||||||
|
setTimeout(() => status.set(""), 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Options state
|
||||||
|
const options = {
|
||||||
|
styleAsObject: {
|
||||||
|
title: "Convert style to object",
|
||||||
|
value: S(true),
|
||||||
|
},
|
||||||
|
dataAttrsAsCamel: {
|
||||||
|
title: "dataKey/ariaKey (or dataset/ariaset)",
|
||||||
|
value: S(true),
|
||||||
|
},
|
||||||
|
indent: {
|
||||||
|
title: "Indentation (-1 for tabs)",
|
||||||
|
value: S("-1"),
|
||||||
|
type: "number",
|
||||||
|
},
|
||||||
|
expaned: {
|
||||||
|
title: "Force multiline",
|
||||||
|
value: S(false),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const getOptions = ()=> Object.fromEntries(Object.entries(options)
|
||||||
|
.map(([key, option]) => ([
|
||||||
|
key,
|
||||||
|
option.value.get()
|
||||||
|
]))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update the dd<el> output when input or options change
|
||||||
|
const ddeOutput = S(() => {
|
||||||
|
try {
|
||||||
|
const result = convertHTMLtoDDE(htmlInput.get(), getOptions());
|
||||||
|
error.set("");
|
||||||
|
return result;
|
||||||
|
} catch (err) {
|
||||||
|
error.set(`Error: ${err.message}`);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Event handlers
|
||||||
|
const onConvert = on("submit", e => {
|
||||||
|
e.preventDefault();
|
||||||
|
htmlInput.set(htmlInput.get(), true);
|
||||||
|
showStatus("Converted!");
|
||||||
|
});
|
||||||
|
|
||||||
|
const onCopy = on("click", async () => {
|
||||||
|
if (!ddeOutput.get()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(ddeOutput.get());
|
||||||
|
showStatus("Copied to clipboard!");
|
||||||
|
} catch (err) {
|
||||||
|
error.set(`Could not copy: ${err.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const onClear = on("click", () => {
|
||||||
|
htmlInput.set("");
|
||||||
|
showStatus("Input cleared");
|
||||||
|
});
|
||||||
|
const onExampleLoad = (example) => on("click", () => {
|
||||||
|
htmlInput.set(example.html);
|
||||||
|
showStatus(`Loaded "${example.name}" example`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const optionsElements = () => Object.entries(options)
|
||||||
|
.map(([key, option]) =>
|
||||||
|
el("label", { className: "option-group" }).append(
|
||||||
|
option.type==="number"
|
||||||
|
? el("input", {
|
||||||
|
type: option.type || "checkbox",
|
||||||
|
name: key,
|
||||||
|
value: option.value.get(),
|
||||||
|
max: 10,
|
||||||
|
}, on("change", e => option.value.set(e.target.value)))
|
||||||
|
: el("input", {
|
||||||
|
type: option.type || "checkbox",
|
||||||
|
name: key,
|
||||||
|
checked: option.value.get(),
|
||||||
|
}, on("change", e => option.value.set(e.target.checked))),
|
||||||
|
option.title,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const exampleButtons = examples.map(example =>
|
||||||
|
el("button", {
|
||||||
|
type: "button",
|
||||||
|
className: "secondary example-button"
|
||||||
|
}, onExampleLoad(example)).append(example.name)
|
||||||
|
);
|
||||||
|
|
||||||
|
return el("div", { id: "html-to-dde-converter" }).append(
|
||||||
|
el("h3", "HTML to dd<el> Converter"),
|
||||||
|
el("p", { className: "description" }).append(
|
||||||
|
"Convert HTML markup to dd<el> JavaScript code. Paste your HTML below or choose from an example."
|
||||||
|
),
|
||||||
|
|
||||||
|
el("form", { className: "converter-form" }, onConvert).append(
|
||||||
|
el("div", { className: "options" }).append(...optionsElements()),
|
||||||
|
|
||||||
|
el("div", { className: "examples-list" }).append(
|
||||||
|
el("label", "Examples: "),
|
||||||
|
...exampleButtons
|
||||||
|
),
|
||||||
|
|
||||||
|
el("div", { className: "editor-container" }).append(
|
||||||
|
el("div", { className: "input-group" }).append(
|
||||||
|
el("label", { htmlFor: "html-input" }).append(
|
||||||
|
"HTML Input",
|
||||||
|
el("div", { className: "button-group" }).append(
|
||||||
|
el("button", {
|
||||||
|
type: "button",
|
||||||
|
className: "secondary",
|
||||||
|
title: "Clear input"
|
||||||
|
}, onClear).append("Clear")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
el("textarea", {
|
||||||
|
id: "html-input",
|
||||||
|
spellcheck: false,
|
||||||
|
value: htmlInput,
|
||||||
|
placeholder: "Paste your HTML here or choose an example",
|
||||||
|
oninput: e => htmlInput.set(e.target.value)
|
||||||
|
})
|
||||||
|
),
|
||||||
|
|
||||||
|
el("div", { className: "output-group" }).append(
|
||||||
|
el("label", { htmlFor: "dde-output" }).append(
|
||||||
|
"dd<el> Output",
|
||||||
|
el("div", { className: "button-group" }).append(
|
||||||
|
el("button", {
|
||||||
|
textContent: "Copy",
|
||||||
|
type: "button",
|
||||||
|
className: "copy-button",
|
||||||
|
title: "Copy to clipboard",
|
||||||
|
disabled: S(() => !ddeOutput.get())
|
||||||
|
}, onCopy)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
el("textarea", {
|
||||||
|
id: "dde-output",
|
||||||
|
readonly: true,
|
||||||
|
spellcheck: false,
|
||||||
|
placeholder: "The converted dd<el> code will appear here",
|
||||||
|
value: S(() => ddeOutput.get() || "// Convert HTML to see results here")
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
el("div", { className: "button-group" }).append(
|
||||||
|
S.el(error, error => !error ? el() : el("div", { className: "error" }).append(error)),
|
||||||
|
el("div", { className: "status", textContent: status }),
|
||||||
|
el("button", { type: "submit" }).append("Convert")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
@ -1,9 +1,8 @@
|
|||||||
import { styles } from "../ssr.js";
|
import { page_id, styles } from "../ssr.js";
|
||||||
const host= "."+example.name;
|
const host= "."+example.name;
|
||||||
styles.css`
|
styles.css`
|
||||||
${host} {
|
${host} {
|
||||||
grid-column: full-main;
|
grid-column: full-main;
|
||||||
width: calc(100% - .75em);
|
|
||||||
height: calc(4/6 * var(--body-max-width));
|
height: calc(4/6 * var(--body-max-width));
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
@ -84,7 +83,6 @@ html[data-theme="light"] .cm-s-material .cm-error { color: #f44336 !important; }
|
|||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
${host} {
|
${host} {
|
||||||
height: 50vh;
|
height: 50vh;
|
||||||
max-width: 100%;
|
|
||||||
}
|
}
|
||||||
${host} main {
|
${host} main {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
@ -96,6 +94,18 @@ html[data-theme="light"] .cm-s-material .cm-error { color: #f44336 !important; }
|
|||||||
max-width: 100% !important;
|
max-width: 100% !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
${host}[data-variant=big]{
|
||||||
|
height: 150vh;
|
||||||
|
|
||||||
|
main {
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
main > * {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const dde_content= s.cat(new URL("../../dist/esm-with-signals.js", import.meta.url)).toString();
|
const dde_content= s.cat(new URL("../../dist/esm-with-signals.js", import.meta.url)).toString();
|
||||||
@ -108,15 +118,15 @@ import { relative } from "node:path";
|
|||||||
* @param {object} attrs
|
* @param {object} attrs
|
||||||
* @param {URL} attrs.src Example code file path
|
* @param {URL} attrs.src Example code file path
|
||||||
* @param {"js"|"ts"|"html"|"css"} [attrs.language="js"] Language of the code
|
* @param {"js"|"ts"|"html"|"css"} [attrs.language="js"] Language of the code
|
||||||
* @param {string} attrs.page_id ID of the page
|
* @param {"normal"|"big"} [attrs.variant="normal"] Size of the example
|
||||||
* */
|
* */
|
||||||
export function example({ src, language= "js", page_id }){
|
export function example({ src, language= "js", variant= "normal" }){
|
||||||
registerClientPart(page_id);
|
registerClientPart(page_id);
|
||||||
const content= s.cat(src).toString()
|
const content= s.cat(src).toString()
|
||||||
.replaceAll(/ from "deka-dom-el(\/signals)?";/g, ' from "./esm-with-signals.js";');
|
.replaceAll(/ from "deka-dom-el(\/signals)?";/g, ' from "./esm-with-signals.js";');
|
||||||
const id= "code-example-"+generateCodeId(src);
|
const id= "code-example-"+generateCodeId(src);
|
||||||
return el().append(
|
return el().append(
|
||||||
el(code, { id, content, language, className: example.name }),
|
el(code, { id, content, language, className: example.name }, el=> el.dataset.variant= variant),
|
||||||
elCode({ id, content, extension: "."+language })
|
elCode({ id, content, extension: "."+language })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
375
docs/components/examples/case-studies/data-dashboard.js
Normal file
375
docs/components/examples/case-studies/data-dashboard.js
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
/**
|
||||||
|
* Case Study: Data Dashboard with Charts
|
||||||
|
*
|
||||||
|
* This example demonstrates:
|
||||||
|
* - Integration with a third-party charting library
|
||||||
|
* - Data fetching and state management
|
||||||
|
* - Responsive layout design
|
||||||
|
* - Multiple interactive components working together
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { el, on } from "deka-dom-el";
|
||||||
|
import { S } from "deka-dom-el/signals";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Dashboard Component with Chart Integration
|
||||||
|
* @returns {HTMLElement} Dashboard element
|
||||||
|
*/
|
||||||
|
export function DataDashboard() {
|
||||||
|
// Mock data for demonstration
|
||||||
|
const DATA = {
|
||||||
|
sales: [42, 58, 65, 49, 72, 85, 63, 70, 78, 89, 95, 86],
|
||||||
|
visitors: [1420, 1620, 1750, 1850, 2100, 2400, 2250, 2500, 2750, 2900, 3100, 3200],
|
||||||
|
conversion: [2.9, 3.5, 3.7, 2.6, 3.4, 3.5, 2.8, 2.8, 2.8, 3.1, 3.0, 2.7],
|
||||||
|
months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||||
|
};
|
||||||
|
const years = [2022, 2023, 2024];
|
||||||
|
const dataTypes = [
|
||||||
|
{ id: 'sales', label: 'Sales', unit: 'K' },
|
||||||
|
{ id: 'visitors', label: 'Visitors', unit: '' },
|
||||||
|
{ id: 'conversion', label: 'Conversion Rate', unit: '%' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Filter options
|
||||||
|
const selectedYear = S(2024);
|
||||||
|
const onYearChange = on("change", e => {
|
||||||
|
selectedYear.set(parseInt(/** @type {HTMLSelectElement} */(e.target).value));
|
||||||
|
loadData();
|
||||||
|
});
|
||||||
|
const selectedDataType = S(/** @type {'sales' | 'visitors' | 'conversion'} */ ('sales'));
|
||||||
|
const onDataTypeChange = on("click", e => {
|
||||||
|
const type = /** @type {'sales' | 'visitors' | 'conversion'} */(
|
||||||
|
/** @type {HTMLButtonElement} */(e.currentTarget).dataset.type);
|
||||||
|
selectedDataType.set(type);
|
||||||
|
});
|
||||||
|
const currentDataType = S(() => dataTypes.find(type => type.id === selectedDataType.get()));
|
||||||
|
const selectedData = S(() => DATA[selectedDataType.get()]);
|
||||||
|
|
||||||
|
// Values based on filters
|
||||||
|
const totalValue = S(() => selectedData.get().reduce((sum, value) => sum + value, 0));
|
||||||
|
const averageValue = S(() => {
|
||||||
|
const data = selectedData.get();
|
||||||
|
return data.reduce((sum, value) => sum + value, 0) / data.length;
|
||||||
|
});
|
||||||
|
const highestValue = S(() => Math.max(...selectedData.get()));
|
||||||
|
|
||||||
|
// Simulate data loading
|
||||||
|
const isLoading = S(false);
|
||||||
|
const error = S(null);
|
||||||
|
function loadData() {
|
||||||
|
isLoading.set(true);
|
||||||
|
error.set(null);
|
||||||
|
|
||||||
|
// Simulate API call
|
||||||
|
setTimeout(() => {
|
||||||
|
if (Math.random() > 0.9) {
|
||||||
|
// Simulate occasional error
|
||||||
|
error.set('Failed to load data. Please try again.');
|
||||||
|
}
|
||||||
|
isLoading.set(false);
|
||||||
|
}, 800);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reactive chart rendering
|
||||||
|
const chart = S(()=> {
|
||||||
|
const chart= el("canvas", { id: "chart-canvas", width: 800, height: 400 });
|
||||||
|
const ctx = chart.getContext('2d');
|
||||||
|
const data = selectedData.get();
|
||||||
|
const months = DATA.months;
|
||||||
|
const width = chart.width;
|
||||||
|
const height = chart.height;
|
||||||
|
const maxValue = Math.max(...data) * 1.1;
|
||||||
|
const barWidth = width / data.length - 10;
|
||||||
|
|
||||||
|
// Clear canvas
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
// Draw background grid
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.strokeStyle = '#f0f0f0';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
for(let i = 0; i < 5; i++) {
|
||||||
|
const y = height - (height * (i / 5)) - 30;
|
||||||
|
ctx.moveTo(50, y);
|
||||||
|
ctx.lineTo(width - 20, y);
|
||||||
|
|
||||||
|
// Draw grid labels
|
||||||
|
ctx.fillStyle = '#999';
|
||||||
|
ctx.font = '12px Arial';
|
||||||
|
ctx.fillText(Math.round(maxValue * (i / 5)).toString(), 20, y + 5);
|
||||||
|
}
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Draw bars
|
||||||
|
data.forEach((value, index) => {
|
||||||
|
const x = index * (barWidth + 10) + 60;
|
||||||
|
const barHeight = (value / maxValue) * (height - 60);
|
||||||
|
|
||||||
|
// Bar
|
||||||
|
ctx.fillStyle = '#4a90e2';
|
||||||
|
ctx.fillRect(x, height - barHeight - 30, barWidth, barHeight);
|
||||||
|
|
||||||
|
// Month label
|
||||||
|
ctx.fillStyle = '#666';
|
||||||
|
ctx.font = '12px Arial';
|
||||||
|
ctx.fillText(months[index], x + barWidth/2 - 10, height - 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Chart title
|
||||||
|
ctx.fillStyle = '#333';
|
||||||
|
ctx.font = 'bold 14px Arial';
|
||||||
|
ctx.fillText(`${currentDataType.get().label} (${selectedYear.get()})`, width/2 - 80, 20);
|
||||||
|
return chart;
|
||||||
|
});
|
||||||
|
|
||||||
|
return el("div", { className: "dashboard" }).append(
|
||||||
|
el("header", { className: "dashboard-header" }).append(
|
||||||
|
el("h1", "Sales Performance Dashboard"),
|
||||||
|
el("div", { className: "year-filter" }).append(
|
||||||
|
el("label", { htmlFor: "yearSelect", textContent: "Select Year:" }),
|
||||||
|
el("select", { id: "yearSelect" },
|
||||||
|
on.defer(el=> el.value = selectedYear.get().toString()),
|
||||||
|
onYearChange
|
||||||
|
).append(
|
||||||
|
...years.map(year => el("option", { value: year, textContent: year }))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
S.el(error, errorMsg => !errorMsg
|
||||||
|
? el()
|
||||||
|
: el("div", { className: "error-message" }).append(
|
||||||
|
el("p", errorMsg),
|
||||||
|
el("button", { textContent: "Retry", type: "button" }, on("click", loadData)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
S.el(isLoading, loading => !loading
|
||||||
|
? el()
|
||||||
|
: el("div", { className: "loading-spinner" })
|
||||||
|
),
|
||||||
|
|
||||||
|
// Main dashboard content
|
||||||
|
el("div", { className: "dashboard-content" }).append(
|
||||||
|
// Metrics cards
|
||||||
|
el("div", { className: "metrics-container" }).append(
|
||||||
|
el("div", { className: "metric-card" }).append(
|
||||||
|
el("h3", "Total"),
|
||||||
|
el("#text", S(() => `${totalValue.get().toLocaleString()}${currentDataType.get().unit}`)),
|
||||||
|
),
|
||||||
|
el("div", { className: "metric-card" }).append(
|
||||||
|
el("h3", "Average"),
|
||||||
|
el("#text", S(() => `${averageValue.get().toFixed(1)}${currentDataType.get().unit}`)),
|
||||||
|
),
|
||||||
|
el("div", { className: "metric-card" }).append(
|
||||||
|
el("h3", "Highest"),
|
||||||
|
el("#text", S(() => `${highestValue.get()}${currentDataType.get().unit}`)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Data type selection tabs
|
||||||
|
el("div", { className: "data-type-tabs" }).append(
|
||||||
|
...dataTypes.map(type =>
|
||||||
|
el("button", {
|
||||||
|
type: "button",
|
||||||
|
className: S(() => selectedDataType.get() === type.id ? 'active' : ''),
|
||||||
|
dataType: type.id,
|
||||||
|
textContent: type.label
|
||||||
|
}, onDataTypeChange)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
// Chart container
|
||||||
|
el("div", { className: "chart-container" }).append(
|
||||||
|
S.el(chart, chart => chart)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the component
|
||||||
|
document.body.append(
|
||||||
|
el("div", { style: "padding: 20px; background: #f5f5f5; min-height: 100vh;" }).append(
|
||||||
|
el(DataDashboard)
|
||||||
|
),
|
||||||
|
el("style", `
|
||||||
|
.dashboard {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 1rem;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-header h1 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin: 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year-filter {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year-filter select {
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metrics-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-card {
|
||||||
|
background: #f9f9f9;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-card h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-card p {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-type-tabs {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-type-tabs button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #666;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-type-tabs button.active {
|
||||||
|
color: #4a90e2;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-type-tabs button.active::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -1px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 3px;
|
||||||
|
background: #4a90e2;
|
||||||
|
border-radius: 3px 3px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner::before {
|
||||||
|
content: '';
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 4px solid #f3f3f3;
|
||||||
|
border-top: 4px solid #4a90e2;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
background: #ffecec;
|
||||||
|
color: #e74c3c;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message button {
|
||||||
|
background: #e74c3c;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.metrics-container {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year-filter {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year-filter select {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
);
|
412
docs/components/examples/case-studies/image-gallery.js
Normal file
412
docs/components/examples/case-studies/image-gallery.js
Normal file
@ -0,0 +1,412 @@
|
|||||||
|
/**
|
||||||
|
* Case Study: Interactive Image Gallery
|
||||||
|
*
|
||||||
|
* This example demonstrates:
|
||||||
|
* - Dynamic loading of content
|
||||||
|
* - Lightbox functionality
|
||||||
|
* - Animation handling
|
||||||
|
* - Keyboard and gesture navigation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { el, memo, on } from "deka-dom-el";
|
||||||
|
import { S } from "deka-dom-el/signals";
|
||||||
|
|
||||||
|
// Sample image data
|
||||||
|
const imagesSample = (url=> [
|
||||||
|
{ id: 1, src: url+'nature', alt: 'Nature', title: 'Beautiful Landscape' },
|
||||||
|
{ id: 2, src: url+'places', alt: 'City', title: 'Urban Architecture' },
|
||||||
|
{ id: 3, src: url+'people', alt: 'People', title: 'Street Photography' },
|
||||||
|
{ id: 4, src: url+'food', alt: 'Food', title: 'Culinary Delights' },
|
||||||
|
{ id: 5, src: url+'animals', alt: 'Animals', title: 'Wildlife' },
|
||||||
|
{ id: 6, src: url+'travel', alt: 'Travel', title: 'Adventure Awaits' },
|
||||||
|
{ id: 7, src: url+'computer', alt: 'Technology', title: 'Modern Tech' },
|
||||||
|
{ id: 8, src: url+'music', alt: 'Art', title: 'Creative Expression' },
|
||||||
|
])('https://api.algobook.info/v1/randomimage?category=');
|
||||||
|
/**
|
||||||
|
* Interactive Image Gallery Component
|
||||||
|
* @returns {HTMLElement} Gallery element
|
||||||
|
*/
|
||||||
|
export function ImageGallery(images= imagesSample) {
|
||||||
|
const filterTag = S('all');
|
||||||
|
const imagesToDisplay = S(() => {
|
||||||
|
const tag = filterTag.get();
|
||||||
|
if (tag === 'all') return images;
|
||||||
|
else return images.filter(img => img.alt.toLowerCase() === tag);
|
||||||
|
})
|
||||||
|
const onFilterChange = tag => on("click", () => {
|
||||||
|
filterTag.set(tag);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Lightbox
|
||||||
|
const selectedImageId = S(null);
|
||||||
|
const selectedImage = S(() => {
|
||||||
|
const id = selectedImageId.get();
|
||||||
|
return id ? images.find(img => img.id === id) : null;
|
||||||
|
});
|
||||||
|
const isLightboxOpen = S(() => selectedImage.get() !== null);
|
||||||
|
const onImageClick = id => on("click", () => {
|
||||||
|
selectedImageId.set(id);
|
||||||
|
document.body.style.overflow = 'hidden'; // Prevent scrolling when lightbox is open
|
||||||
|
|
||||||
|
// Add keyboard event listeners when lightbox opens
|
||||||
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
|
});
|
||||||
|
const closeLightbox = () => {
|
||||||
|
selectedImageId.set(null);
|
||||||
|
document.body.style.overflow = ''; // Restore scrolling
|
||||||
|
|
||||||
|
// Remove keyboard event listeners when lightbox closes
|
||||||
|
document.removeEventListener('keydown', handleKeyDown);
|
||||||
|
};
|
||||||
|
const onPrevImage = e => {
|
||||||
|
e.stopPropagation(); // Prevent closing the lightbox
|
||||||
|
const images = imagesToDisplay.get();
|
||||||
|
const currentId = selectedImageId.get();
|
||||||
|
const currentIndex = images.findIndex(img => img.id === currentId);
|
||||||
|
const prevIndex = (currentIndex - 1 + images.length) % images.length;
|
||||||
|
selectedImageId.set(images[prevIndex].id);
|
||||||
|
};
|
||||||
|
const onNextImage = e => {
|
||||||
|
e.stopPropagation(); // Prevent closing the lightbox
|
||||||
|
const images = imagesToDisplay.get();
|
||||||
|
const currentId = selectedImageId.get();
|
||||||
|
const currentIndex = images.findIndex(img => img.id === currentId);
|
||||||
|
const nextIndex = (currentIndex + 1) % images.length;
|
||||||
|
selectedImageId.set(images[nextIndex].id);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Keyboard navigation handler
|
||||||
|
function handleKeyDown(e) {
|
||||||
|
switch(e.key) {
|
||||||
|
case 'Escape':
|
||||||
|
closeLightbox();
|
||||||
|
break;
|
||||||
|
case 'ArrowLeft':
|
||||||
|
onPrevImage(e);
|
||||||
|
break;
|
||||||
|
case 'ArrowRight':
|
||||||
|
onNextImage(e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the gallery UI
|
||||||
|
return el("div", { className: "gallery-container" }).append(
|
||||||
|
// Gallery header
|
||||||
|
el("header", { className: "gallery-header" }).append(
|
||||||
|
el("h1", "Interactive Image Gallery"),
|
||||||
|
el("p", "Click on any image to view it in the lightbox. Use arrow keys for navigation.")
|
||||||
|
),
|
||||||
|
|
||||||
|
// Filter options
|
||||||
|
el("div", { className: "gallery-filters" }).append(
|
||||||
|
el("button", {
|
||||||
|
classList: { active: S(() => filterTag.get() === 'all') },
|
||||||
|
textContent: "All"
|
||||||
|
}, onFilterChange('all')),
|
||||||
|
el("button", {
|
||||||
|
classList: { active: S(() => filterTag.get() === 'nature') },
|
||||||
|
textContent: "Nature"
|
||||||
|
}, onFilterChange('nature')),
|
||||||
|
el("button", {
|
||||||
|
classList: { active: S(() => filterTag.get() === 'urban') },
|
||||||
|
textContent: "Urban"
|
||||||
|
}, onFilterChange('urban')),
|
||||||
|
el("button", {
|
||||||
|
classList: { active: S(() => filterTag.get() === 'people') },
|
||||||
|
textContent: "People"
|
||||||
|
}, onFilterChange('people'))
|
||||||
|
),
|
||||||
|
|
||||||
|
// Image grid
|
||||||
|
el("div", { className: "gallery-grid" }).append(
|
||||||
|
S.el(imagesToDisplay, images =>
|
||||||
|
images.map(image =>
|
||||||
|
memo(image.id, ()=>
|
||||||
|
el("div", {
|
||||||
|
className: "gallery-item",
|
||||||
|
dataTag: image.alt.toLowerCase()
|
||||||
|
}, onImageClick(image.id)).append(
|
||||||
|
el("img", {
|
||||||
|
src: image.src,
|
||||||
|
alt: image.alt,
|
||||||
|
loading: "lazy"
|
||||||
|
}),
|
||||||
|
el("div", { className: "gallery-item-caption" }).append(
|
||||||
|
el("h3", image.title),
|
||||||
|
el("p", image.alt)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
// Lightbox (only shown when an image is selected)
|
||||||
|
S.el(isLightboxOpen, open => !open
|
||||||
|
? el()
|
||||||
|
: el("div", { className: "lightbox-overlay" }, on("click", closeLightbox)).append(
|
||||||
|
el("div", {
|
||||||
|
className: "lightbox-content",
|
||||||
|
onClick: e => e.stopPropagation() // Prevent closing when clicking inside
|
||||||
|
}).append(
|
||||||
|
el("button", {
|
||||||
|
className: "lightbox-close-btn",
|
||||||
|
ariaLabel: "Close lightbox",
|
||||||
|
}, on("click", closeLightbox)).append("×"),
|
||||||
|
|
||||||
|
el("button", {
|
||||||
|
className: "lightbox-prev-btn",
|
||||||
|
ariaLabel: "Previous image",
|
||||||
|
}, on("click", onPrevImage)).append("❮"),
|
||||||
|
|
||||||
|
el("button", {
|
||||||
|
className: "lightbox-next-btn",
|
||||||
|
ariaLabel: "Next image",
|
||||||
|
}, on("click", onNextImage)).append("❯"),
|
||||||
|
|
||||||
|
S.el(selectedImage, img => !img
|
||||||
|
? el()
|
||||||
|
: el("div", { className: "lightbox-image-container" }).append(
|
||||||
|
el("img", {
|
||||||
|
src: img.src,
|
||||||
|
alt: img.alt,
|
||||||
|
className: "lightbox-image",
|
||||||
|
}),
|
||||||
|
el("div", { className: "lightbox-caption" }).append(
|
||||||
|
el("h2", img.title),
|
||||||
|
el("p", img.alt),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the component
|
||||||
|
document.body.append(
|
||||||
|
el("div", { style: "padding: 20px; background: #f5f5f5; min-height: 100vh;" }).append(
|
||||||
|
el(ImageGallery)
|
||||||
|
),
|
||||||
|
el("style", `
|
||||||
|
.gallery-container {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-header h1 {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-header p {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-filters {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-filters button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 30px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-filters button:hover {
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-filters button.active {
|
||||||
|
background: #4a90e2;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-item {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-item:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-item img {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
transition: transform 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-item:hover img {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-item-caption {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
|
||||||
|
color: white;
|
||||||
|
padding: 1rem;
|
||||||
|
transform: translateY(100%);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-item:hover .gallery-item-caption {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-item-caption h3 {
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-item-caption p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lightbox styles */
|
||||||
|
.lightbox-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.9);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1000;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-content {
|
||||||
|
position: relative;
|
||||||
|
max-width: 90%;
|
||||||
|
max-height: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-image-container {
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 0 30px rgba(0, 0, 0, 0.5);
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-image {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 80vh;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-caption {
|
||||||
|
background: #222;
|
||||||
|
color: white;
|
||||||
|
padding: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-caption h2 {
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-caption p {
|
||||||
|
margin: 0;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-close-btn,
|
||||||
|
.lightbox-prev-btn,
|
||||||
|
.lightbox-next-btn {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.3s ease;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-close-btn:hover,
|
||||||
|
.lightbox-prev-btn:hover,
|
||||||
|
.lightbox-next-btn:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-close-btn {
|
||||||
|
top: -25px;
|
||||||
|
right: -25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-prev-btn {
|
||||||
|
left: -25px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-next-btn {
|
||||||
|
right: -25px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.gallery-container {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-prev-btn,
|
||||||
|
.lightbox-next-btn {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
);
|
339
docs/components/examples/case-studies/interactive-form.js
Normal file
339
docs/components/examples/case-studies/interactive-form.js
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
/**
|
||||||
|
* Case Study: Interactive Form with Validation
|
||||||
|
*
|
||||||
|
* This example demonstrates:
|
||||||
|
* - Form handling with real-time validation
|
||||||
|
* - Reactive UI updates based on input state
|
||||||
|
* - Complex form state management
|
||||||
|
* - Clean separation of concerns (data, validation, UI)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { dispatchEvent, el, on, scope } from "deka-dom-el";
|
||||||
|
import { S } from "deka-dom-el/signals";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} FormState
|
||||||
|
* @property {string} name
|
||||||
|
* @property {string} email
|
||||||
|
* @property {string} password
|
||||||
|
* @property {string} confirmPassword
|
||||||
|
* @property {boolean} agreedToTerms
|
||||||
|
* */
|
||||||
|
/**
|
||||||
|
* Interactive Form with Validation Component
|
||||||
|
* @returns {HTMLElement} Form element
|
||||||
|
*/
|
||||||
|
export function InteractiveForm() {
|
||||||
|
const submitted = S(false);
|
||||||
|
/** @type {FormState|null} */
|
||||||
|
let formState = null;
|
||||||
|
/** @param {CustomEvent<FormState>} event */
|
||||||
|
const onSubmit = ({ detail }) => {
|
||||||
|
submitted.set(true);
|
||||||
|
formState = detail;
|
||||||
|
};
|
||||||
|
const onAnotherAccount = () => {
|
||||||
|
submitted.set(false)
|
||||||
|
formState = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return el("div", { className: "form-container" }).append(
|
||||||
|
S.el(submitted, s => s
|
||||||
|
? el("div", { className: "success-message" }).append(
|
||||||
|
el("h3", "Thank you for registering!"),
|
||||||
|
el("p", `Welcome, ${formState.name}! Your account has been created successfully.`),
|
||||||
|
el("button", { textContent: "Register another account", type: "button" },
|
||||||
|
on("click", onAnotherAccount)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: el(Form, { initial: formState }, on("form:submit", onSubmit))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Form Component
|
||||||
|
* @type {(props: { initial: FormState | null }) => HTMLElement}
|
||||||
|
* */
|
||||||
|
export function Form({ initial }) {
|
||||||
|
const { host }= scope;
|
||||||
|
// Form state management
|
||||||
|
const formState = S(initial || {
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: '',
|
||||||
|
agreedToTerms: false
|
||||||
|
}, {
|
||||||
|
/**
|
||||||
|
* @template {keyof FormState} K
|
||||||
|
* @param {K} key
|
||||||
|
* @param {FormState[K]} value
|
||||||
|
* */
|
||||||
|
update(key, value) {
|
||||||
|
this.value[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* Event handler for input events
|
||||||
|
* @param {"value"|"checked"} prop
|
||||||
|
* @returns {(ev: Event) => void}
|
||||||
|
* */
|
||||||
|
const onChange= prop => ev => {
|
||||||
|
const input = /** @type {HTMLInputElement} */(ev.target);
|
||||||
|
S.action(formState, "update", /** @type {keyof FormState} */(input.id), input[prop]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Form validate state
|
||||||
|
const nameValid = S(() => formState.get().name.length >= 3);
|
||||||
|
const emailValid = S(() => {
|
||||||
|
const email = formState.get().email;
|
||||||
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
||||||
|
});
|
||||||
|
const passwordValid = S(() => {
|
||||||
|
const password = formState.get().password;
|
||||||
|
return password.length >= 8 && /[A-Z]/.test(password) && /[0-9]/.test(password);
|
||||||
|
});
|
||||||
|
const passwordsMatch = S(() => {
|
||||||
|
const { password, confirmPassword } = formState.get();
|
||||||
|
return password === confirmPassword && confirmPassword !== '';
|
||||||
|
});
|
||||||
|
const termsAgreed = S(() => formState.get().agreedToTerms);
|
||||||
|
const formValid = S(() =>
|
||||||
|
nameValid.get() &&
|
||||||
|
emailValid.get() &&
|
||||||
|
passwordValid.get() &&
|
||||||
|
passwordsMatch.get() &&
|
||||||
|
termsAgreed.get()
|
||||||
|
);
|
||||||
|
|
||||||
|
const dispatcSubmit = dispatchEvent("form:submit", host);
|
||||||
|
const onSubmit = on("submit", e => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!formValid.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatcSubmit(formState.get());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Component UI
|
||||||
|
return el("form", { className: "registration-form" }, onSubmit).append(
|
||||||
|
el("h2", "Create an Account"),
|
||||||
|
|
||||||
|
// Name field
|
||||||
|
el("div", { classList: {
|
||||||
|
"form-group": true,
|
||||||
|
valid: nameValid,
|
||||||
|
invalid: S(()=> !nameValid.get() && formState.get().name)
|
||||||
|
}}).append(
|
||||||
|
el("label", { htmlFor: "name", textContent: "Full Name" }),
|
||||||
|
el("input", {
|
||||||
|
id: "name",
|
||||||
|
type: "text",
|
||||||
|
value: formState.get().name,
|
||||||
|
placeholder: "Enter your full name"
|
||||||
|
}, on("input", onChange("value"))),
|
||||||
|
el("div", { className: "validation-message", textContent: "Name must be at least 3 characters long" }),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Email field
|
||||||
|
el("div", { classList: {
|
||||||
|
"form-group": true,
|
||||||
|
valid: emailValid,
|
||||||
|
invalid: S(()=> !emailValid.get() && formState.get().email)
|
||||||
|
}}).append(
|
||||||
|
el("label", { htmlFor: "email", textContent: "Email Address" }),
|
||||||
|
el("input", {
|
||||||
|
id: "email",
|
||||||
|
type: "email",
|
||||||
|
value: formState.get().email,
|
||||||
|
placeholder: "Enter your email address"
|
||||||
|
}, on("input", onChange("value"))),
|
||||||
|
el("div", { className: "validation-message", textContent: "Please enter a valid email address" })
|
||||||
|
),
|
||||||
|
|
||||||
|
// Password field
|
||||||
|
el("div", { classList: {
|
||||||
|
"form-group": true,
|
||||||
|
valid: passwordValid,
|
||||||
|
invalid: S(()=> !passwordValid.get() && formState.get().password)
|
||||||
|
}}).append(
|
||||||
|
el("label", { htmlFor: "password", textContent: "Password" }),
|
||||||
|
el("input", {
|
||||||
|
id: "password",
|
||||||
|
type: "password",
|
||||||
|
value: formState.get().password,
|
||||||
|
placeholder: "Create a password"
|
||||||
|
}, on("input", onChange("value"))),
|
||||||
|
el("div", {
|
||||||
|
className: "validation-message",
|
||||||
|
textContent: "Password must be at least 8 characters with at least one uppercase letter and one number",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Confirm password field
|
||||||
|
el("div", { classList: {
|
||||||
|
"form-group": true,
|
||||||
|
valid: passwordsMatch,
|
||||||
|
invalid: S(()=> !passwordsMatch.get() && formState.get().confirmPassword)
|
||||||
|
}}).append(
|
||||||
|
el("label", { htmlFor: "confirmPassword", textContent: "Confirm Password" }),
|
||||||
|
el("input", {
|
||||||
|
id: "confirmPassword",
|
||||||
|
type: "password",
|
||||||
|
value: formState.get().confirmPassword,
|
||||||
|
placeholder: "Confirm your password"
|
||||||
|
}, on("input", onChange("value"))),
|
||||||
|
el("div", { className: "validation-message", textContent: "Passwords must match" }),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Terms agreement
|
||||||
|
el("div", { className: "form-group checkbox-group" }).append(
|
||||||
|
el("input", {
|
||||||
|
id: "agreedToTerms",
|
||||||
|
type: "checkbox",
|
||||||
|
checked: formState.get().agreedToTerms
|
||||||
|
}, on("change", onChange("checked"))),
|
||||||
|
el("label", { htmlFor: "agreedToTerms", textContent: "I agree to the Terms and Conditions" }),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Submit button
|
||||||
|
el("button", {
|
||||||
|
textContent: "Create Account",
|
||||||
|
type: "submit",
|
||||||
|
className: "submit-button",
|
||||||
|
disabled: S(() => !formValid.get())
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the component
|
||||||
|
document.body.append(
|
||||||
|
el("div", { style: "padding: 20px; background: #f5f5f5; min-height: 100vh;" }).append(
|
||||||
|
el(InteractiveForm)
|
||||||
|
),
|
||||||
|
el("style", `
|
||||||
|
.form-container {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
position: relative;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #555;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
input[type="email"],
|
||||||
|
input[type="password"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: border-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #4a90e2;
|
||||||
|
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group label {
|
||||||
|
margin: 0 0 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.validation-message {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #e74c3c;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group.invalid .validation-message {
|
||||||
|
height: auto;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group.valid input {
|
||||||
|
border-color: #2ecc71;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group.invalid input {
|
||||||
|
border-color: #e74c3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-button {
|
||||||
|
background-color: #4a90e2;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-button:hover:not(:disabled) {
|
||||||
|
background-color: #3a7bc8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-button:disabled {
|
||||||
|
background-color: #b5b5b5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-message {
|
||||||
|
text-align: center;
|
||||||
|
color: #2ecc71;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-message h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-message button {
|
||||||
|
background-color: #2ecc71;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-message button:hover {
|
||||||
|
background-color: #27ae60;
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
);
|
509
docs/components/examples/case-studies/products.js
Normal file
509
docs/components/examples/case-studies/products.js
Normal file
@ -0,0 +1,509 @@
|
|||||||
|
import { el, on, scope } from "deka-dom-el";
|
||||||
|
import { S } from "deka-dom-el/signals";
|
||||||
|
|
||||||
|
export function ProductCatalog() {
|
||||||
|
const { signal }= scope;
|
||||||
|
|
||||||
|
const itemsPerPage = 5;
|
||||||
|
const products = asyncSignal(S,
|
||||||
|
fetchProducts,
|
||||||
|
{ initial: [], keepLast: true, signal });
|
||||||
|
const searchTerm = S("");
|
||||||
|
const handleSearch = (e) => searchTerm.set(e.target.value);
|
||||||
|
const sortOrder = S("default");
|
||||||
|
const handleSort = (e) => sortOrder.set(e.target.value);
|
||||||
|
const page = S(1);
|
||||||
|
const handlePageChange = (newPage) => page.set(newPage);
|
||||||
|
const resetFilters = () => {
|
||||||
|
searchTerm.set("");
|
||||||
|
sortOrder.set("default");
|
||||||
|
page.set(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredProducts = S(() => {
|
||||||
|
if (products.status.get() !== "resolved") return [];
|
||||||
|
|
||||||
|
const results = products.result.get().filter(product =>
|
||||||
|
product.title.toLowerCase().includes(searchTerm.get().toLowerCase()) ||
|
||||||
|
product.description.toLowerCase().includes(searchTerm.get().toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
return [...results].sort((a, b) => {
|
||||||
|
const order = sortOrder.get();
|
||||||
|
if (order === "price-asc") return a.price - b.price;
|
||||||
|
if (order === "price-desc") return b.price - a.price;
|
||||||
|
if (order === "rating") return b.rating - a.rating;
|
||||||
|
return 0; // default: no sorting
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const totalPages = S(() => Math.ceil(filteredProducts.get().length / itemsPerPage));
|
||||||
|
const paginatedProducts = S(() => {
|
||||||
|
const currentPage = page.get();
|
||||||
|
const filtered = filteredProducts.get();
|
||||||
|
const start = (currentPage - 1) * itemsPerPage;
|
||||||
|
return filtered.slice(start, start + itemsPerPage);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Component structure
|
||||||
|
return el("div", { className: "product-catalog" }).append(
|
||||||
|
el("header", { className: "catalog-header" }).append(
|
||||||
|
el("h2", "Product Catalog"),
|
||||||
|
el("div", { className: "toolbar" }).append(
|
||||||
|
el("button", {
|
||||||
|
className: "refresh-btn",
|
||||||
|
textContent: "Refresh Products",
|
||||||
|
type: "button",
|
||||||
|
onclick: () => products.invoke(),
|
||||||
|
}),
|
||||||
|
el("button", {
|
||||||
|
className: "reset-btn",
|
||||||
|
textContent: "Reset Filters",
|
||||||
|
type: "button",
|
||||||
|
onclick: resetFilters,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
// Search and filter controls
|
||||||
|
el("div", { className: "controls" }).append(
|
||||||
|
el("div", { className: "search-box" }).append(
|
||||||
|
el("input", {
|
||||||
|
type: "search",
|
||||||
|
placeholder: "Search products...",
|
||||||
|
value: searchTerm,
|
||||||
|
oninput: handleSearch,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
el("div", { className: "sort-options" }).append(
|
||||||
|
el("label", "Sort by: "),
|
||||||
|
el("select", { onchange: handleSort }, on.defer(el => el.value = sortOrder.get())).append(
|
||||||
|
el("option", { value: "default", textContent: "Default" }),
|
||||||
|
el("option", { value: "price-asc", textContent: "Price: Low to High" }),
|
||||||
|
el("option", { value: "price-desc", textContent: "Price: High to Low" }),
|
||||||
|
el("option", { value: "rating", textContent: "Top Rated" })
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
// Status indicators
|
||||||
|
el("div", { className: "status-container" }).append(
|
||||||
|
S.el(products.status, status =>
|
||||||
|
status === "pending" ?
|
||||||
|
el("div", { className: "loader" }).append(
|
||||||
|
el("div", { className: "spinner" }),
|
||||||
|
el("p", "Loading products...")
|
||||||
|
)
|
||||||
|
: status === "rejected" ?
|
||||||
|
el("div", { className: "error-message" }).append(
|
||||||
|
el("p", products.error.get().message),
|
||||||
|
el("button", {
|
||||||
|
textContent: "Try Again",
|
||||||
|
onclick: () => products.invoke()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
: el()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
// Results count
|
||||||
|
S.el(S(()=> [filteredProducts.get(), searchTerm.get()]), ([filtered, term]) =>
|
||||||
|
products.status.get() === "resolved"
|
||||||
|
? el("div", {
|
||||||
|
className: "results-info",
|
||||||
|
textContent: term ?
|
||||||
|
`Found ${filtered.length} products matching "${term}"`
|
||||||
|
: `Showing all ${filtered.length} products`
|
||||||
|
})
|
||||||
|
: el()
|
||||||
|
),
|
||||||
|
|
||||||
|
// Products grid
|
||||||
|
el("div", { className: "products-grid" }).append(
|
||||||
|
S.el(paginatedProducts, paginatedItems =>
|
||||||
|
products.status.get() === "resolved" && paginatedItems.length > 0 ?
|
||||||
|
paginatedItems.map(product => el(ProductCard, { product }))
|
||||||
|
: products.status.get() === "resolved" && paginatedItems.length === 0 ?
|
||||||
|
el("p", { className: "no-results", textContent: "No products found matching your criteria." })
|
||||||
|
: el()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
S.el(S(()=> [totalPages.get(), page.get()]), ([total, current]) =>
|
||||||
|
products.status.get() === "resolved" && total > 1 ?
|
||||||
|
el("div", { className: "pagination" }).append(
|
||||||
|
el("button", {
|
||||||
|
textContent: "Previous",
|
||||||
|
disabled: current === 1,
|
||||||
|
onclick: () => handlePageChange(current - 1)
|
||||||
|
}),
|
||||||
|
...Array.from({ length: total }, (_, i) => i + 1).map(num =>
|
||||||
|
el("button", {
|
||||||
|
className: num === current ? "current-page" : "",
|
||||||
|
textContent: num,
|
||||||
|
onclick: () => handlePageChange(num)
|
||||||
|
})
|
||||||
|
),
|
||||||
|
el("button", {
|
||||||
|
textContent: "Next",
|
||||||
|
disabled: current === total,
|
||||||
|
onclick: () => handlePageChange(current + 1)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
: el()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Product card component
|
||||||
|
function ProductCard({ product }) {
|
||||||
|
const showDetails = S(false);
|
||||||
|
|
||||||
|
return el("div", { className: "product-card" }).append(
|
||||||
|
el("div", { className: "product-image" }).append(
|
||||||
|
el("img", { src: product.thumbnail, alt: product.title })
|
||||||
|
),
|
||||||
|
el("div", { className: "product-info" }).append(
|
||||||
|
el("h3", { className: "product-title", textContent: product.title }),
|
||||||
|
el("div", { className: "product-price-rating" }).append(
|
||||||
|
el("span", { className: "product-price", textContent: `$${product.price.toFixed(2)}` }),
|
||||||
|
el("span", { className: "product-rating" }).append(
|
||||||
|
el("span", { className: "stars", textContent: "★".repeat(Math.round(product.rating)) }),
|
||||||
|
el("span", { className: "rating-value", textContent: `(${product.rating})` }),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
el("p", { className: "product-category", textContent: `Category: ${product.category}` }),
|
||||||
|
S.el(showDetails, details =>
|
||||||
|
details ?
|
||||||
|
el("div", { className: "product-details" }).append(
|
||||||
|
el("p", { className: "product-description", textContent: product.description }),
|
||||||
|
el("div", { className: "product-meta" }).append(
|
||||||
|
el("p", `Brand: ${product.brand}`),
|
||||||
|
el("p", `Stock: ${product.stock} units`),
|
||||||
|
el("p", `Discount: ${product.discountPercentage}%`)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: el()
|
||||||
|
),
|
||||||
|
el("div", { className: "product-actions" }).append(
|
||||||
|
el("button", {
|
||||||
|
className: "details-btn",
|
||||||
|
textContent: S(() => showDetails.get() ? "Hide Details" : "Show Details"),
|
||||||
|
onclick: () => showDetails.set(!showDetails.get())
|
||||||
|
}),
|
||||||
|
el("button", {
|
||||||
|
className: "add-to-cart-btn",
|
||||||
|
textContent: "Add to Cart"
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data fetching function
|
||||||
|
async function fetchProducts({ signal }) {
|
||||||
|
await simulateNetworkDelay();
|
||||||
|
// Simulate random errors for demonstration
|
||||||
|
if (Math.random() > 0.9) throw new Error("Failed to load products. Network error.");
|
||||||
|
|
||||||
|
const response = await fetch("https://dummyjson.com/products", { signal });
|
||||||
|
if (!response.ok) throw new Error(`API error: ${response.status}`);
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data.products.slice(0, 20); // Limit to 20 products for the demo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility for simulating network latency
|
||||||
|
function simulateNetworkDelay(min = 300, max = 1200) {
|
||||||
|
const delay = Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
return new Promise(resolve => setTimeout(resolve, delay));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook for async data fetching with signals
|
||||||
|
* @template T
|
||||||
|
* @param {typeof S} S - Signal constructor
|
||||||
|
* @param {(params: { signal: AbortSignal }) => Promise<T>} invoker - Async function to execute
|
||||||
|
* @param {{ initial?: T, keepLast?: boolean, signal?: AbortSignal }} options - Configuration options
|
||||||
|
* @returns {Object} Status signals and control methods
|
||||||
|
*/
|
||||||
|
export function asyncSignal(S, invoker, { initial, keepLast, signal } = {}) {
|
||||||
|
/** @type {(s: AbortSignal) => AbortSignal} */
|
||||||
|
const anySignal = !signal || !AbortSignal.any // TODO: make better
|
||||||
|
? s=> s
|
||||||
|
: s=> AbortSignal.any([s, signal]);
|
||||||
|
// Status tracking signals
|
||||||
|
const status = S("pending");
|
||||||
|
const result = S(initial);
|
||||||
|
const error = S(null);
|
||||||
|
let controller = null;
|
||||||
|
|
||||||
|
// Function to trigger data fetching
|
||||||
|
async function invoke() {
|
||||||
|
// Cancel any in-flight request
|
||||||
|
if (controller) controller.abort();
|
||||||
|
controller = new AbortController();
|
||||||
|
|
||||||
|
status.set("pending");
|
||||||
|
error.set(null);
|
||||||
|
if (!keepLast) result.set(initial);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await invoker({
|
||||||
|
signal: anySignal(controller.signal),
|
||||||
|
});
|
||||||
|
if (!controller.signal.aborted) {
|
||||||
|
status.set("resolved");
|
||||||
|
result.set(data);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e.name !== "AbortError") {
|
||||||
|
error.set(e);
|
||||||
|
status.set("rejected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial data fetch
|
||||||
|
invoke();
|
||||||
|
|
||||||
|
return { status, result, error, invoke };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the component
|
||||||
|
document.body.append(
|
||||||
|
el(ProductCatalog),
|
||||||
|
el("style", `
|
||||||
|
.product-catalog {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar button {
|
||||||
|
margin-left: 10px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: none;
|
||||||
|
background: #4a6cf7;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
gap: 15px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box input {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 300px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-options select {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
display: inline-block;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||||
|
border-left-color: #4a6cf7;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
background: #ffebee;
|
||||||
|
color: #c62828;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 20px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-info {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.products-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card {
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image img {
|
||||||
|
width: 100%;
|
||||||
|
height: 180px;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-info {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-title {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
height: 2.4rem;
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-price-rating {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-price {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #4a6cf7;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stars {
|
||||||
|
color: gold;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-category {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-details {
|
||||||
|
margin: 15px 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-description {
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-actions button {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-btn {
|
||||||
|
background: #eee;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-to-cart-btn {
|
||||||
|
background: #4a6cf7;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 5px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination button {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
background: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination button.current-page {
|
||||||
|
background: #4a6cf7;
|
||||||
|
color: white;
|
||||||
|
border-color: #4a6cf7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination button:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-results {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.controls {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.products-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
);
|
715
docs/components/examples/case-studies/task-manager.js
Normal file
715
docs/components/examples/case-studies/task-manager.js
Normal file
@ -0,0 +1,715 @@
|
|||||||
|
/**
|
||||||
|
* Case Study: Task Manager Application
|
||||||
|
*
|
||||||
|
* This example demonstrates:
|
||||||
|
* - Complex state management with signals
|
||||||
|
* - Drag and drop functionality
|
||||||
|
* - Local storage persistence
|
||||||
|
* - Responsive design for different devices
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { el, on, dispatchEvent, scope } from "deka-dom-el";
|
||||||
|
import { S } from "deka-dom-el/signals";
|
||||||
|
|
||||||
|
/** @typedef {{ id: number, title: string, description: string, priority: string, status: string }} Task */
|
||||||
|
/**
|
||||||
|
* Task Manager Component
|
||||||
|
* @returns {HTMLElement} Task manager UI
|
||||||
|
*/
|
||||||
|
export function TaskManager() {
|
||||||
|
// <Tasks store>
|
||||||
|
const STORAGE_KEY = 'dde-task-manager';
|
||||||
|
const STATUSES = {
|
||||||
|
TODO: 'todo',
|
||||||
|
IN_PROGRESS: 'in-progress',
|
||||||
|
DONE: 'done'
|
||||||
|
};
|
||||||
|
/** @type {Task[]} */
|
||||||
|
let initialTasks = [];
|
||||||
|
try {
|
||||||
|
const saved = localStorage.getItem(STORAGE_KEY);
|
||||||
|
if (saved) {
|
||||||
|
initialTasks = JSON.parse(saved);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to load tasks from localStorage', e);
|
||||||
|
}
|
||||||
|
if (!initialTasks.length) {
|
||||||
|
initialTasks = [
|
||||||
|
{ id: 1, title: 'Create project structure', description: 'Set up folders and initial files',
|
||||||
|
status: STATUSES.DONE, priority: 'high' },
|
||||||
|
{ id: 2, title: 'Design UI components', description: 'Create mockups for main views',
|
||||||
|
status: STATUSES.IN_PROGRESS, priority: 'medium' },
|
||||||
|
{ id: 3, title: 'Implement authentication', description: 'Set up user login and registration',
|
||||||
|
status: STATUSES.TODO, priority: 'high' },
|
||||||
|
{ id: 4, title: 'Write documentation', description: 'Document API endpoints and usage examples',
|
||||||
|
status: STATUSES.TODO, priority: 'low' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
const tasks = S(initialTasks, {
|
||||||
|
add(task) { this.value.push(task); },
|
||||||
|
remove(id) { this.value = this.value.filter(task => task.id !== id); },
|
||||||
|
update(id, task) {
|
||||||
|
const current= this.value.find(t => t.id === id);
|
||||||
|
if (current) Object.assign(current, task);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
S.on(tasks, value => {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(value));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to save tasks to localStorage', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// </Tasks store>
|
||||||
|
|
||||||
|
const filterPriority = S('all');
|
||||||
|
const searchQuery = S('');
|
||||||
|
// Filtered tasks based on priority and search query
|
||||||
|
const filteredTasks = S(() => {
|
||||||
|
let filtered = tasks.get();
|
||||||
|
|
||||||
|
// Filter by priority
|
||||||
|
if (filterPriority.get() !== 'all') {
|
||||||
|
filtered = filtered.filter(task => task.priority === filterPriority.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by search query
|
||||||
|
const query = searchQuery.get().toLowerCase();
|
||||||
|
if (query) {
|
||||||
|
filtered = filtered.filter(task =>
|
||||||
|
task.title.toLowerCase().includes(query) ||
|
||||||
|
task.description.toLowerCase().includes(query)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
});
|
||||||
|
/** Tasks grouped by status for display in columns */
|
||||||
|
const tasksByStatus = S(() => {
|
||||||
|
const filtered = filteredTasks.get();
|
||||||
|
return {
|
||||||
|
[STATUSES.TODO]: filtered.filter(t => t.status === STATUSES.TODO),
|
||||||
|
[STATUSES.IN_PROGRESS]: filtered.filter(t => t.status === STATUSES.IN_PROGRESS),
|
||||||
|
[STATUSES.DONE]: filtered.filter(t => t.status === STATUSES.DONE)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// <Add> signals and handlers for adding new tasks
|
||||||
|
const newTask = { title: '', description: '', priority: 'medium' };
|
||||||
|
const onAddTask = e => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!newTask.title) return;
|
||||||
|
|
||||||
|
S.action(tasks, "add", {
|
||||||
|
id: Date.now(),
|
||||||
|
status: STATUSES.TODO,
|
||||||
|
...newTask
|
||||||
|
});
|
||||||
|
e.target.reset();
|
||||||
|
};
|
||||||
|
// </Add>
|
||||||
|
const onCardEdit= on("card:edit", /** @param {CardEditEvent} ev */({ detail: [ id, task ] })=>
|
||||||
|
S.action(tasks, "update", id, task));
|
||||||
|
const onCardDelete= on("card:delete", /** @param {CardDeleteEvent} ev */({ detail: id })=>
|
||||||
|
S.action(tasks, "remove", id));
|
||||||
|
|
||||||
|
const { onDragable, onDragArea }= moveElementAddon(
|
||||||
|
(id, status) => S.action(tasks, "update", id, { status })
|
||||||
|
);
|
||||||
|
|
||||||
|
// Build the task manager UI
|
||||||
|
return el("div", { className: "task-manager" }).append(
|
||||||
|
el("header", { className: "app-header" }).append(
|
||||||
|
el("h1", "DDE Task Manager"),
|
||||||
|
el("div", { className: "app-controls" }).append(
|
||||||
|
el("input", {
|
||||||
|
type: "text",
|
||||||
|
placeholder: "Search tasks...",
|
||||||
|
value: searchQuery.get()
|
||||||
|
}, on("input", e => searchQuery.set(e.target.value))),
|
||||||
|
el("select", null,
|
||||||
|
on.defer(el=> el.value= filterPriority.get()),
|
||||||
|
on("change", e => filterPriority.set(e.target.value))
|
||||||
|
).append(
|
||||||
|
el("option", { value: "all", textContent: "All Priorities" }),
|
||||||
|
el("option", { value: "low", textContent: "Low Priority" }),
|
||||||
|
el("option", { value: "medium", textContent: "Medium Priority" }),
|
||||||
|
el("option", { value: "high", textContent: "High Priority" })
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
// Add new task form
|
||||||
|
el("form", { className: "new-task-form" }, on("submit", onAddTask)).append(
|
||||||
|
el("div", { className: "form-row" }).append(
|
||||||
|
el("input", {
|
||||||
|
type: "text",
|
||||||
|
placeholder: "New task title",
|
||||||
|
value: newTask.title,
|
||||||
|
required: true
|
||||||
|
}, on("input", e => newTask.title= e.target.value.trim())),
|
||||||
|
el("select", null,
|
||||||
|
on.defer(el=> el.value= newTask.priority),
|
||||||
|
on("change", e => newTask.priority= e.target.value)
|
||||||
|
).append(
|
||||||
|
el("option", { value: "low", textContent: "Low" }),
|
||||||
|
el("option", { value: "medium", textContent: "Medium" }),
|
||||||
|
el("option", { value: "high", textContent: "High" })
|
||||||
|
),
|
||||||
|
el("button", { type: "submit", className: "add-btn" }).append("Add Task")
|
||||||
|
),
|
||||||
|
el("textarea", {
|
||||||
|
placeholder: "Task description (optional)",
|
||||||
|
value: newTask.description
|
||||||
|
}, on("input", e => newTask.description= e.target.value.trim()))
|
||||||
|
),
|
||||||
|
|
||||||
|
// Task board with columns
|
||||||
|
el("div", { className: "task-board" }).append(
|
||||||
|
// Todo column
|
||||||
|
el("div", {
|
||||||
|
id: `column-${STATUSES.TODO}`,
|
||||||
|
className: "task-column"
|
||||||
|
}, onDragArea(STATUSES.TODO)).append(
|
||||||
|
el("h2", { className: "column-header" }).append(
|
||||||
|
"To Do ",
|
||||||
|
el("span", {
|
||||||
|
textContent: S(() => tasksByStatus.get()[STATUSES.TODO].length),
|
||||||
|
className: "task-count"
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
S.el(S(() => tasksByStatus.get()[STATUSES.TODO]), tasks =>
|
||||||
|
el("div", { className: "column-tasks" }).append(
|
||||||
|
...tasks.map(task=> el(TaskCard, { task, onDragable }, onCardEdit, onCardDelete))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
// In Progress column
|
||||||
|
el("div", {
|
||||||
|
id: `column-${STATUSES.IN_PROGRESS}`,
|
||||||
|
className: "task-column"
|
||||||
|
}, onDragArea(STATUSES.IN_PROGRESS)).append(
|
||||||
|
el("h2", { className: "column-header" }).append(
|
||||||
|
"In Progress ",
|
||||||
|
el("span", {
|
||||||
|
textContent: S(() => tasksByStatus.get()[STATUSES.IN_PROGRESS].length),
|
||||||
|
className: "task-count",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
S.el(S(() => tasksByStatus.get()[STATUSES.IN_PROGRESS]), tasks =>
|
||||||
|
el("div", { className: "column-tasks" }).append(
|
||||||
|
...tasks.map(task=> el(TaskCard, { task, onDragable }, onCardEdit, onCardDelete))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
// Done column
|
||||||
|
el("div", {
|
||||||
|
id: `column-${STATUSES.DONE}`,
|
||||||
|
className: "task-column"
|
||||||
|
}, onDragArea(STATUSES.DONE)).append(
|
||||||
|
el("h2", { className: "column-header" }).append(
|
||||||
|
"Done ",
|
||||||
|
el("span", {
|
||||||
|
textContent: S(() => tasksByStatus.get()[STATUSES.DONE].length),
|
||||||
|
className: "task-count",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
S.el(S(() => tasksByStatus.get()[STATUSES.DONE]), tasks =>
|
||||||
|
el("div", { className: "column-tasks" }).append(
|
||||||
|
...tasks.map(task=> el(TaskCard, { task, onDragable }, onCardEdit, onCardDelete))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/** @typedef {CustomEvent<[ string, Task ]>} CardEditEvent */
|
||||||
|
/** @typedef {CustomEvent<string>} CardDeleteEvent */
|
||||||
|
/**
|
||||||
|
* Task Card Component
|
||||||
|
* @type {(props: { task: Task, onDragable: (id: number) => ddeElementAddon<HTMLDivElement> }) => HTMLElement}
|
||||||
|
* @fires {CardEditEvent} card:edit
|
||||||
|
* @fires {CardDeleteEvent} card:delete
|
||||||
|
* */
|
||||||
|
function TaskCard({ task, onDragable }){
|
||||||
|
const { host }= scope;
|
||||||
|
const isEditing = S(false);
|
||||||
|
const onEditStart = () => isEditing.set(true);
|
||||||
|
|
||||||
|
const dispatchEdit= dispatchEvent("card:edit", host);
|
||||||
|
const dispatchDelete= dispatchEvent("card:delete", host).bind(null, task.id);
|
||||||
|
|
||||||
|
return el("div", {
|
||||||
|
id: `task-${task.id}`,
|
||||||
|
className: `task-card priority-${task.priority}`,
|
||||||
|
draggable: true
|
||||||
|
}, onDragable(task.id)).append(
|
||||||
|
S.el(isEditing, editing => editing
|
||||||
|
? el(EditMode)
|
||||||
|
: el().append(
|
||||||
|
el("div", { className: "task-header" }).append(
|
||||||
|
el("h3", { className: "task-title", textContent: task.title }),
|
||||||
|
el("div", { className: "task-actions" }).append(
|
||||||
|
el("button", {
|
||||||
|
textContent: "✎",
|
||||||
|
className: "edit-btn",
|
||||||
|
ariaLabel: "Edit task"
|
||||||
|
}, on("click", onEditStart)),
|
||||||
|
el("button", {
|
||||||
|
textContent: "✕",
|
||||||
|
className: "delete-btn",
|
||||||
|
ariaLabel: "Delete task"
|
||||||
|
}, on("click", dispatchDelete))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
!task.description
|
||||||
|
? el()
|
||||||
|
: el("p", { className: "task-description", textContent: task.description }),
|
||||||
|
el("div", { className: "task-meta" }).append(
|
||||||
|
el("span", {
|
||||||
|
className: `priority-badge priority-${task.priority}`,
|
||||||
|
textContent: task.priority.charAt(0).toUpperCase() + task.priority.slice(1)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
function EditMode(){
|
||||||
|
const onSubmit = on("submit", e => {
|
||||||
|
e.preventDefault();
|
||||||
|
const formData = new FormData(/** @type {HTMLFormElement} */(e.target));
|
||||||
|
const title = formData.get("title");
|
||||||
|
const description = formData.get("description");
|
||||||
|
const priority = formData.get("priority");
|
||||||
|
isEditing.set(false);
|
||||||
|
dispatchEdit([ task.id, { title, description, priority } ]);
|
||||||
|
})
|
||||||
|
const onEditCancel = () => isEditing.set(false);
|
||||||
|
|
||||||
|
return el("form", { className: "task-edit-form" }, onSubmit).append(
|
||||||
|
el("input", {
|
||||||
|
name: "title",
|
||||||
|
className: "task-title-input",
|
||||||
|
defaultValue: task.title,
|
||||||
|
placeholder: "Task title",
|
||||||
|
required: true,
|
||||||
|
autoFocus: true
|
||||||
|
}),
|
||||||
|
el("textarea", {
|
||||||
|
name: "description",
|
||||||
|
className: "task-desc-input",
|
||||||
|
defaultValue: task.description,
|
||||||
|
placeholder: "Description (optional)"
|
||||||
|
}),
|
||||||
|
el("select", {
|
||||||
|
name: "priority",
|
||||||
|
}, on.defer(el=> el.value = task.priority)).append(
|
||||||
|
el("option", { value: "low", textContent: "Low Priority" }),
|
||||||
|
el("option", { value: "medium", textContent: "Medium Priority" }),
|
||||||
|
el("option", { value: "high", textContent: "High Priority" })
|
||||||
|
),
|
||||||
|
el("div", { className: "task-edit-actions" }).append(
|
||||||
|
el("button", {
|
||||||
|
textContent: "Cancel",
|
||||||
|
type: "button",
|
||||||
|
className: "cancel-btn"
|
||||||
|
}, on("click", onEditCancel)),
|
||||||
|
el("button", {
|
||||||
|
textContent: "Save",
|
||||||
|
type: "submit",
|
||||||
|
className: "save-btn"
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to handle move an element
|
||||||
|
* @param {(id: string, status: string) => void} onMoved
|
||||||
|
* */
|
||||||
|
function moveElementAddon(onMoved){
|
||||||
|
let draggedTaskId = null;
|
||||||
|
function onDragable(id) {
|
||||||
|
return element => {
|
||||||
|
on("dragstart", e => {
|
||||||
|
draggedTaskId= id;
|
||||||
|
e.dataTransfer.effectAllowed = 'move';
|
||||||
|
|
||||||
|
// Add some styling to the element being dragged
|
||||||
|
setTimeout(() => {
|
||||||
|
const el = document.getElementById(`task-${id}`);
|
||||||
|
if (el) el.classList.add('dragging');
|
||||||
|
}, 0);
|
||||||
|
})(element);
|
||||||
|
|
||||||
|
on("dragend", () => {
|
||||||
|
draggedTaskId= null;
|
||||||
|
|
||||||
|
// Remove the styling
|
||||||
|
const el = document.getElementById(`task-${id}`);
|
||||||
|
if (el) el.classList.remove('dragging');
|
||||||
|
})(element);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function onDragArea(status) {
|
||||||
|
return element => {
|
||||||
|
on("dragover", e => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = 'move';
|
||||||
|
|
||||||
|
// Add a visual indicator for the drop target
|
||||||
|
const column = document.getElementById(`column-${status}`);
|
||||||
|
if (column) column.classList.add('drag-over');
|
||||||
|
})(element);
|
||||||
|
|
||||||
|
on("dragleave", () => {
|
||||||
|
// Remove the visual indicator
|
||||||
|
const column = document.getElementById(`column-${status}`);
|
||||||
|
if (column) column.classList.remove('drag-over');
|
||||||
|
})(element);
|
||||||
|
|
||||||
|
on("drop", e => {
|
||||||
|
e.preventDefault();
|
||||||
|
const id = draggedTaskId;
|
||||||
|
if (id) onMoved(id, status);
|
||||||
|
// Remove the visual indicator
|
||||||
|
const column = document.getElementById(`column-${status}`);
|
||||||
|
if (column) column.classList.remove('drag-over');
|
||||||
|
})(element);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { onDragable, onDragArea };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the component
|
||||||
|
document.body.append(
|
||||||
|
el("div", { style: "padding: 20px; background: #f5f5f5; min-height: 100vh;" }).append(
|
||||||
|
el(TaskManager)
|
||||||
|
),
|
||||||
|
el("style", `
|
||||||
|
.task-manager {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 1rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header h1 {
|
||||||
|
margin: 0;
|
||||||
|
color: #2d3748;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-controls input,
|
||||||
|
.app-controls select {
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-task-form {
|
||||||
|
background: white;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row input {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row select {
|
||||||
|
width: 100px;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn {
|
||||||
|
background: #4a90e2;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn:hover {
|
||||||
|
background: #3a7bc8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-task-form textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-board {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-column {
|
||||||
|
background: #f7fafc;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
min-height: 400px;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-header {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-bottom: 0.75rem;
|
||||||
|
border-bottom: 2px solid #e2e8f0;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: #2d3748;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-count {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: #e2e8f0;
|
||||||
|
color: #4a5568;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-tasks {
|
||||||
|
margin-top: 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 1rem;
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
|
||||||
|
cursor: grab;
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
position: relative;
|
||||||
|
border-left: 4px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-card:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-card.dragging {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-card.priority-low {
|
||||||
|
border-left-color: #38b2ac;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-card.priority-medium {
|
||||||
|
border-left-color: #ecc94b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-card.priority-high {
|
||||||
|
border-left-color: #e53e3e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #2d3748;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-description {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #4a5568;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-btn,
|
||||||
|
.delete-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: #718096;
|
||||||
|
transition: background 0.2s ease, color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-btn:hover {
|
||||||
|
background: #edf2f7;
|
||||||
|
color: #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-btn:hover {
|
||||||
|
background: #fed7d7;
|
||||||
|
color: #e53e3e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-meta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.priority-badge {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.priority-badge.priority-low {
|
||||||
|
background: #e6fffa;
|
||||||
|
color: #2c7a7b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.priority-badge.priority-medium {
|
||||||
|
background: #fefcbf;
|
||||||
|
color: #975a16;
|
||||||
|
}
|
||||||
|
|
||||||
|
.priority-badge.priority-high {
|
||||||
|
background: #fed7d7;
|
||||||
|
color: #c53030;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-over {
|
||||||
|
background: #f0f9ff;
|
||||||
|
border: 2px dashed #4a90e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-edit-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-title-input,
|
||||||
|
.task-desc-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-desc-input {
|
||||||
|
min-height: 60px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-edit-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn,
|
||||||
|
.save-btn {
|
||||||
|
padding: 0.4rem 0.75rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
background: #edf2f7;
|
||||||
|
color: #4a5568;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-btn {
|
||||||
|
background: #4a90e2;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.app-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-controls {
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-board {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
);
|
@ -8,7 +8,8 @@ export class HTMLCustomElement extends HTMLElement{
|
|||||||
connectedCallback(){
|
connectedCallback(){
|
||||||
customElementRender(
|
customElementRender(
|
||||||
this.attachShadow({ mode: "open" }),
|
this.attachShadow({ mode: "open" }),
|
||||||
ddeComponent
|
ddeComponent,
|
||||||
|
this
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
set attr(value){ this.setAttribute("attr", value); }
|
set attr(value){ this.setAttribute("attr", value); }
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
import {
|
import {
|
||||||
customElementRender,
|
customElementRender,
|
||||||
customElementWithDDE,
|
customElementWithDDE,
|
||||||
observedAttributes,
|
|
||||||
} from "deka-dom-el";
|
} from "deka-dom-el";
|
||||||
/** @type {ddePublicElementTagNameMap} */
|
/** @type {ddePublicElementTagNameMap} */
|
||||||
import { S } from "deka-dom-el/signals";
|
import { S } from "deka-dom-el/signals";
|
||||||
|
@ -9,7 +9,7 @@ export class HTMLCustomElement extends HTMLElement{
|
|||||||
// nice place to render custom element
|
// nice place to render custom element
|
||||||
}
|
}
|
||||||
attributeChangedCallback(name, oldValue, newValue){
|
attributeChangedCallback(name, oldValue, newValue){
|
||||||
// listen to attribute changes (see `observedAttributes`)
|
// listen to attribute changes (see `S.observedAttributes`)
|
||||||
}
|
}
|
||||||
disconnectedCallback(){
|
disconnectedCallback(){
|
||||||
// nice place to clean up
|
// nice place to clean up
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
customElementRender,
|
customElementRender,
|
||||||
customElementWithDDE,
|
customElementWithDDE,
|
||||||
observedAttributes,
|
|
||||||
el, on, scope,
|
el, on, scope,
|
||||||
} from "deka-dom-el";
|
} from "deka-dom-el";
|
||||||
import { S } from "deka-dom-el/signals";
|
import { S } from "deka-dom-el/signals";
|
||||||
@ -9,7 +8,6 @@ export class HTMLCustomElement extends HTMLElement{
|
|||||||
static tagName= "custom-element";
|
static tagName= "custom-element";
|
||||||
static observedAttributes= [ "attr" ];
|
static observedAttributes= [ "attr" ];
|
||||||
connectedCallback(){
|
connectedCallback(){
|
||||||
console.log(observedAttributes(this));
|
|
||||||
customElementRender(
|
customElementRender(
|
||||||
this.attachShadow({ mode: "open" }),
|
this.attachShadow({ mode: "open" }),
|
||||||
ddeComponent,
|
ddeComponent,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { S } from "deka-dom-el/signals";
|
import { S } from "deka-dom-el/signals";
|
||||||
// Debouncing signal updates
|
|
||||||
|
// ===== Approach 1: Traditional debouncing with utility function =====
|
||||||
function debounce(func, wait) {
|
function debounce(func, wait) {
|
||||||
let timeout;
|
let timeout;
|
||||||
return (...args)=> {
|
return (...args)=> {
|
||||||
@ -13,3 +14,54 @@ const debouncedSet= debounce(value => inputSignal.set(value), 300);
|
|||||||
|
|
||||||
// In your input handler
|
// In your input handler
|
||||||
inputElement.addEventListener("input", e => debouncedSet(e.target.value));
|
inputElement.addEventListener("input", e => debouncedSet(e.target.value));
|
||||||
|
|
||||||
|
// ===== Approach 2: Signal debouncing utility =====
|
||||||
|
/**
|
||||||
|
* Creates a debounced signal that only updates after delay
|
||||||
|
* @param {any} initialValue Initial signal value
|
||||||
|
* @param {number} delay Debounce delay in ms
|
||||||
|
*/
|
||||||
|
function createDebouncedSignal(initialValue, delay = 300) {
|
||||||
|
// Create two signals: one for immediate updates, one for debounced values
|
||||||
|
const immediateSignal = S(initialValue);
|
||||||
|
const debouncedSignal = S(initialValue);
|
||||||
|
|
||||||
|
// Keep track of the timeout
|
||||||
|
let timeout = null;
|
||||||
|
|
||||||
|
// Set up a listener on the immediate signal
|
||||||
|
S.on(immediateSignal, value => {
|
||||||
|
// Clear any existing timeout
|
||||||
|
if (timeout) clearTimeout(timeout);
|
||||||
|
|
||||||
|
// Set a new timeout to update the debounced signal
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
debouncedSignal.set(value);
|
||||||
|
}, delay);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return an object with both signals and a setter function
|
||||||
|
return {
|
||||||
|
// The raw signal that updates immediately
|
||||||
|
raw: immediateSignal,
|
||||||
|
// The debounced signal that only updates after delay
|
||||||
|
debounced: debouncedSignal,
|
||||||
|
// Setter function to update the immediate signal
|
||||||
|
set: value => immediateSignal.set(value)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage example
|
||||||
|
const searchInput = createDebouncedSignal("", 300);
|
||||||
|
|
||||||
|
// Log immediate changes for demonstration
|
||||||
|
S.on(searchInput.raw, value => console.log("Input changed to:", value));
|
||||||
|
|
||||||
|
// Only perform expensive operations on the debounced value
|
||||||
|
S.on(searchInput.debounced, value => {
|
||||||
|
console.log("Performing search with:", value);
|
||||||
|
// Expensive operation would go here
|
||||||
|
});
|
||||||
|
|
||||||
|
// In your input handler
|
||||||
|
searchElement.addEventListener("input", e => searchInput.set(e.target.value));
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Example of reactive element marker
|
// Example of reactive element marker
|
||||||
<!--<dde:mark type=\"reactive\" source=\"...\">-->
|
<!--<dde:mark type="reactive" component="<component-name>">-->
|
||||||
<!-- content that updates when signal changes -->
|
<!-- content that updates when signal changes -->
|
||||||
<!--</dde:mark>-->
|
<!--</dde:mark>-->
|
@ -4,7 +4,7 @@ import { el } from "deka-dom-el";
|
|||||||
const button = el("button", {
|
const button = el("button", {
|
||||||
textContent: "Click me",
|
textContent: "Click me",
|
||||||
className: "primary",
|
className: "primary",
|
||||||
disabled: true
|
disabled: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Shorter and more expressive
|
// Shorter and more expressive
|
||||||
|
@ -6,6 +6,6 @@ import { el } from "deka-dom-el";
|
|||||||
document.body.append(
|
document.body.append(
|
||||||
el("div").append(
|
el("div").append(
|
||||||
el("h1", "Title"),
|
el("h1", "Title"),
|
||||||
el("p", "Paragraph")
|
el("p", "Paragraph"),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
@ -24,7 +24,11 @@ document.body.append(
|
|||||||
);
|
);
|
||||||
|
|
||||||
import { chainableAppend } from "deka-dom-el";
|
import { chainableAppend } from "deka-dom-el";
|
||||||
/** @param {keyof HTMLElementTagNameMap} tag */
|
/**
|
||||||
|
* @template {keyof HTMLElementTagNameMap} TAG
|
||||||
|
* @param {TAG} tag
|
||||||
|
* @returns {ddeHTMLElementTagNameMap[TAG] extends HTMLElement ? ddeHTMLElementTagNameMap[TAG] : ddeHTMLElement}
|
||||||
|
* */
|
||||||
const createElement= tag=> chainableAppend(document.createElement(tag));
|
const createElement= tag=> chainableAppend(document.createElement(tag));
|
||||||
document.body.append(
|
document.body.append(
|
||||||
createElement("p").append(
|
createElement("p").append(
|
||||||
|
@ -15,5 +15,5 @@ const button2 = Object.assign(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Add to DOM
|
// Add to DOM
|
||||||
document.body.appendChild(button);
|
document.body.append(button);
|
||||||
document.body.appendChild(button2);
|
document.body.append(button2);
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
const h1 = document.createElement('h1');
|
const h1 = document.createElement('h1');
|
||||||
h1.textContent = 'Title';
|
h1.textContent = 'Title';
|
||||||
div.appendChild(h1);
|
div.append(h1);
|
||||||
|
|
||||||
const p = document.createElement('p');
|
const p = document.createElement('p');
|
||||||
p.textContent = 'Paragraph';
|
p.textContent = 'Paragraph';
|
||||||
div.appendChild(p);
|
div.append(p);
|
||||||
|
|
||||||
// appendChild doesn't return parent
|
// append doesn't return parent
|
||||||
// so chaining is not possible
|
// so chaining is not possible
|
||||||
|
|
||||||
// Add to DOM
|
// Add to DOM
|
||||||
document.body.appendChild(div);
|
document.body.append(div);
|
||||||
|
19
docs/components/examples/events/dispatch.js
Normal file
19
docs/components/examples/events/dispatch.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { el, on, dispatchEvent, scope } from "deka-dom-el";
|
||||||
|
document.body.append(
|
||||||
|
el(component),
|
||||||
|
);
|
||||||
|
|
||||||
|
function component(){
|
||||||
|
const { host }= scope;
|
||||||
|
const dispatchExample= dispatchEvent(
|
||||||
|
"example",
|
||||||
|
{ bubbles: true },
|
||||||
|
host
|
||||||
|
);
|
||||||
|
|
||||||
|
return el("div").append(
|
||||||
|
el("p", "Dispatch events from outside of the component."),
|
||||||
|
el("button", { textContent: "Dispatch", type: "button" },
|
||||||
|
on("click", dispatchExample))
|
||||||
|
);
|
||||||
|
}
|
@ -1,15 +1,28 @@
|
|||||||
import { el, on } from "deka-dom-el";
|
import { el, on } from "deka-dom-el";
|
||||||
const paragraph= el("p", "See live-cycle events in console.",
|
function allLifecycleEvents(){
|
||||||
|
return el("form", null,
|
||||||
el=> log({ type: "dde:created", detail: el }),
|
el=> log({ type: "dde:created", detail: el }),
|
||||||
on.connected(log),
|
on.connected(log),
|
||||||
on.disconnected(log),
|
on.disconnected(log),
|
||||||
on.attributeChanged(log));
|
).append(
|
||||||
|
el("select", { id: "country" }, on.defer(select => {
|
||||||
|
// This runs when the select is ready with all its options
|
||||||
|
select.value = "cz"; // Pre-select Czechia
|
||||||
|
log({ type: "dde:on.defer", detail: select });
|
||||||
|
})).append(
|
||||||
|
el("option", { value: "au", textContent: "Australia" }),
|
||||||
|
el("option", { value: "ca", textContent: "Canada" }),
|
||||||
|
el("option", { value: "cz", textContent: "Czechia" }),
|
||||||
|
),
|
||||||
|
el("p", "See lifecycle events in console."),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
document.body.append(
|
document.body.append(
|
||||||
paragraph,
|
el(allLifecycleEvents),
|
||||||
el("button", "Update attribute", on("click", ()=> paragraph.setAttribute("test", Math.random().toString()))),
|
el("button", "Remove Element", on("click", function(){
|
||||||
" ",
|
this.previousSibling.remove();
|
||||||
el("button", "Remove", on("click", ()=> paragraph.remove()))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
/** @param {Partial<CustomEvent>} event */
|
/** @param {Partial<CustomEvent>} event */
|
||||||
|
@ -8,7 +8,6 @@ button.addEventListener('click', () => {
|
|||||||
document.querySelector('p').textContent =
|
document.querySelector('p').textContent =
|
||||||
'Clicked ' + count + ' times';
|
'Clicked ' + count + ' times';
|
||||||
|
|
||||||
if (count > 10) {
|
if (count > 10)
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { el, on } from "deka-dom-el";
|
import { el } from "deka-dom-el";
|
||||||
import { S } from "deka-dom-el/signals";
|
import { S } from "deka-dom-el/signals";
|
||||||
|
|
||||||
// A HelloWorld component using the 3PS pattern
|
// A HelloWorld component using the 3PS pattern
|
||||||
|
@ -15,15 +15,15 @@ function HelloWorldComponent({ initial }){
|
|||||||
|
|
||||||
return el().append(
|
return el().append(
|
||||||
el("p", {
|
el("p", {
|
||||||
textContent: S(() => `Hello World ${emoji().repeat(clicks())}`),
|
textContent: S(() => `Hello World ${emoji.get().repeat(clicks.get())}`),
|
||||||
className: "example",
|
className: "example",
|
||||||
ariaLive: "polite", //OR ariaset: { live: "polite" },
|
ariaLive: "polite", //OR ariaset: { live: "polite" },
|
||||||
dataset: { example: "Example" }, //OR dataExample: "Example",
|
dataset: { example: "Example" }, //OR dataExample: "Example",
|
||||||
}),
|
}),
|
||||||
el("button",
|
el("button",
|
||||||
{ textContent: "Fire", type: "button" },
|
{ textContent: "Fire", type: "button" },
|
||||||
on("click", ()=> clicks(clicks() + 1)),
|
on("click", ()=> clicks.set(clicks.get() + 1)),
|
||||||
on("keyup", ()=> clicks(clicks() - 2)),
|
on("keyup", ()=> clicks.set(clicks.get() - 2)),
|
||||||
),
|
),
|
||||||
el("select", null, onChange).append(
|
el("select", null, onChange).append(
|
||||||
el(OptionComponent, "🎉", isSelected),//OR { textContent: "🎉" }
|
el(OptionComponent, "🎉", isSelected),//OR { textContent: "🎉" }
|
||||||
|
2
docs/components/examples/optimization/intro.js
Normal file
2
docs/components/examples/optimization/intro.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// use NPM or for example https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js
|
||||||
|
import { memo } from "deka-dom-el";
|
115
docs/components/examples/optimization/memo.js
Normal file
115
docs/components/examples/optimization/memo.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// Example of how memoization improves performance with list rendering
|
||||||
|
import { el, on, memo } from "deka-dom-el";
|
||||||
|
import { S } from "deka-dom-el/signals";
|
||||||
|
|
||||||
|
// A utility to log element creation
|
||||||
|
function logCreation(name) {
|
||||||
|
console.log(`Creating ${name} element`);
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a signal with our items
|
||||||
|
const itemsSignal = S([
|
||||||
|
{ id: 1, name: "Item 1" },
|
||||||
|
{ id: 2, name: "Item 2" },
|
||||||
|
{ id: 3, name: "Item 3" }
|
||||||
|
], {
|
||||||
|
add() {
|
||||||
|
const { length }= this.value;
|
||||||
|
this.value.push({
|
||||||
|
id: length + 1,
|
||||||
|
name: `Item ${length + 1}`
|
||||||
|
});
|
||||||
|
},
|
||||||
|
force(){},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Without memoization - creates new elements on every render
|
||||||
|
function withoutMemo() {
|
||||||
|
return el("div").append(
|
||||||
|
el("h3", "Without Memoization (check console for element creation)"),
|
||||||
|
el("p", "Elements are recreated on every render"),
|
||||||
|
S.el(itemsSignal, items =>
|
||||||
|
el("ul").append(
|
||||||
|
...items.map(item =>
|
||||||
|
el("li").append(
|
||||||
|
el("span", logCreation(item.name))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// With memoization - reuses elements when possible
|
||||||
|
function withMemo() {
|
||||||
|
return el("div").append(
|
||||||
|
el("h3", "With Memoization (check console for element creation)"),
|
||||||
|
el("p", "Elements are reused when the key (item.id) stays the same"),
|
||||||
|
S.el(itemsSignal, items =>
|
||||||
|
el("ul").append(
|
||||||
|
...items.map(item =>
|
||||||
|
// Use item.id as a stable key for memoization
|
||||||
|
memo(item.id, () =>
|
||||||
|
el("li").append(
|
||||||
|
el("span", logCreation(item.name))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using memo.scope for a custom memoized function
|
||||||
|
const renderMemoList = memo.scope(function(items) {
|
||||||
|
return el("ul").append(
|
||||||
|
...items.map(item =>
|
||||||
|
memo(item.id, () =>
|
||||||
|
el("li").append(
|
||||||
|
el("span", logCreation(`Custom memo: ${item.name}`))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function withCustomMemo() {
|
||||||
|
return el("div").append(
|
||||||
|
el("h3", "With Custom Memo Function"),
|
||||||
|
el("p", "Using memo.scope to create a memoized rendering function"),
|
||||||
|
S.el(itemsSignal, items =>
|
||||||
|
renderMemoList(items)
|
||||||
|
),
|
||||||
|
el("button", "Clear Cache",
|
||||||
|
on("click", () => {
|
||||||
|
renderMemoList.clear();
|
||||||
|
S.action(itemsSignal, "force");
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Demo component showing the difference
|
||||||
|
export function MemoDemo() {
|
||||||
|
return el("div", { style: "padding: 1em; border: 1px solid #ccc;" }).append(
|
||||||
|
el("h2", "Memoization Demo"),
|
||||||
|
el("p", "See in the console when elements are created."),
|
||||||
|
el("p").append(`
|
||||||
|
Notice that without memoization, elements are recreated on every render. With memoization,
|
||||||
|
only new elements are created.
|
||||||
|
`),
|
||||||
|
el("button", "Add Item",
|
||||||
|
on("click", () => S.action(itemsSignal, "add"))
|
||||||
|
),
|
||||||
|
|
||||||
|
el("div", { style: "display: flex; gap: 2em; margin-top: 1em;" }).append(
|
||||||
|
withoutMemo(),
|
||||||
|
withMemo(),
|
||||||
|
withCustomMemo()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.append(el(MemoDemo));
|
381
docs/components/examples/reallife/todomvc.js
Normal file
381
docs/components/examples/reallife/todomvc.js
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
import { dispatchEvent, el, memo, on, scope } from "deka-dom-el";
|
||||||
|
import { S } from "deka-dom-el/signals";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main TodoMVC application component
|
||||||
|
*
|
||||||
|
* Creates and manages the TodoMVC application with the following features:
|
||||||
|
* - Todo items management (add, edit, delete)
|
||||||
|
* - Filtering by status (all, active, completed)
|
||||||
|
* - Client-side routing via URL hash
|
||||||
|
* - Persistent storage with localStorage
|
||||||
|
*
|
||||||
|
* @returns {HTMLElement} The root TodoMVC application element
|
||||||
|
*/
|
||||||
|
function Todos(){
|
||||||
|
const { signal } = scope;
|
||||||
|
const pageS = routerSignal(S, signal);
|
||||||
|
const todosS = todosSignal();
|
||||||
|
/** Derived signal that filters todos based on current route */
|
||||||
|
const todosFilteredS = S(()=> {
|
||||||
|
const todos = todosS.get();
|
||||||
|
const filter = pageS.get();
|
||||||
|
if (filter === "all") return todos;
|
||||||
|
return todos.filter(todo => {
|
||||||
|
if (filter === "active") return !todo.completed;
|
||||||
|
if (filter === "completed") return todo.completed;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const todosRemainingS = S(()=> todosS.get().filter(todo => !todo.completed).length);
|
||||||
|
|
||||||
|
/** @type {ddeElementAddon<HTMLInputElement>} */
|
||||||
|
const onToggleAll = on("change", event => {
|
||||||
|
const checked = /** @type {HTMLInputElement} */ (event.target).checked;
|
||||||
|
S.action(todosS, "completeAll", checked);
|
||||||
|
});
|
||||||
|
const formNewTodo = "newTodo";
|
||||||
|
/** @type {ddeElementAddon<HTMLFormElement>} */
|
||||||
|
const onSubmitNewTodo = on("submit", event => {
|
||||||
|
event.preventDefault();
|
||||||
|
const input = /** @type {HTMLInputElement} */(
|
||||||
|
/** @type {HTMLFormElement} */(event.target).elements.namedItem(formNewTodo)
|
||||||
|
);
|
||||||
|
const title = input.value.trim();
|
||||||
|
if (!title) return;
|
||||||
|
|
||||||
|
S.action(todosS, "add", title);
|
||||||
|
input.value = "";
|
||||||
|
});
|
||||||
|
const onClearCompleted = on("click", () => S.action(todosS, "clearCompleted"));
|
||||||
|
const onDelete = on("todo:delete", ev =>
|
||||||
|
S.action(todosS, "delete", /** @type {{ detail: Todo["id"] }} */(ev).detail));
|
||||||
|
const onEdit = on("todo:edit", ev =>
|
||||||
|
S.action(todosS, "edit", /** @type {{ detail: Partial<Todo> & { id: Todo["id"] } }} */(ev).detail));
|
||||||
|
|
||||||
|
return el("section", { className: "todoapp" }).append(
|
||||||
|
el("header", { className: "header" }).append(
|
||||||
|
el("h1", "todos"),
|
||||||
|
el("form", null, onSubmitNewTodo).append(
|
||||||
|
el("input", {
|
||||||
|
className: "new-todo",
|
||||||
|
name: formNewTodo,
|
||||||
|
placeholder: "What needs to be done?",
|
||||||
|
autocomplete: "off",
|
||||||
|
autofocus: true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
|
S.el(todosS, todos => !todos.length
|
||||||
|
? el()
|
||||||
|
: el("main", { className: "main" }).append(
|
||||||
|
el("input", {
|
||||||
|
id: "toggle-all",
|
||||||
|
className: "toggle-all",
|
||||||
|
type: "checkbox"
|
||||||
|
}, onToggleAll),
|
||||||
|
el("label", { htmlFor: "toggle-all", title: "Mark all as complete" }),
|
||||||
|
el("ul", { className: "todo-list" }).append(
|
||||||
|
S.el(todosFilteredS, filteredTodos => filteredTodos.map(todo =>
|
||||||
|
memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
S.el(todosS, ({ length }) => !length
|
||||||
|
? el()
|
||||||
|
: el("footer", { className: "footer" }).append(
|
||||||
|
el("span", { className: "todo-count" }).append(
|
||||||
|
el("strong", length + " " + (length === 1 ? "item" : "items")),
|
||||||
|
),
|
||||||
|
memo("filters", ()=>
|
||||||
|
el("ul", { className: "filters" }).append(
|
||||||
|
...[ "All", "Active", "Completed" ].map(textContent =>
|
||||||
|
el("li").append(
|
||||||
|
el("a", {
|
||||||
|
textContent,
|
||||||
|
classList: { selected: S(()=> pageS.get() === textContent.toLowerCase()) },
|
||||||
|
href: `#${textContent.toLowerCase()}`
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
length - todosRemainingS.get() === 0
|
||||||
|
? el()
|
||||||
|
: memo("delete", () =>
|
||||||
|
el("button",
|
||||||
|
{ textContent: "Clear completed", className: "clear-completed" },
|
||||||
|
onClearCompleted)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Todo item data structure
|
||||||
|
* @typedef {{ title: string, id: string, completed: boolean }} Todo
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component for rendering an individual todo item
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Display todo with completed state
|
||||||
|
* - Toggle completion status
|
||||||
|
* - Delete todo
|
||||||
|
* - Edit todo with double-click
|
||||||
|
* - Cancel edit with Escape key
|
||||||
|
*
|
||||||
|
* @param {Todo} todo - The todo item data
|
||||||
|
* @fires {void} todo:delete - todo deletion event
|
||||||
|
* @fires {Partial<Todo>} todo:edit - todo edits event
|
||||||
|
*/
|
||||||
|
function TodoItem({ id, title, completed }) {
|
||||||
|
const { host }= scope;
|
||||||
|
const isEditing = S(false);
|
||||||
|
const isCompleted = S(completed);
|
||||||
|
|
||||||
|
/** @type {(id: string) => void} Dispatch function for deleting todo */
|
||||||
|
const dispatchDelete= dispatchEvent("todo:delete", host);
|
||||||
|
/** @type {(data: {id: string, [key: string]: any}) => void} Dispatch function for editing todo */
|
||||||
|
const dispatchEdit = dispatchEvent("todo:edit", host);
|
||||||
|
|
||||||
|
/** @type {ddeElementAddon<HTMLInputElement>} */
|
||||||
|
const onToggleCompleted = on("change", (ev) => {
|
||||||
|
const completed= /** @type {HTMLInputElement} */(ev.target).checked;
|
||||||
|
isCompleted.set(completed);
|
||||||
|
dispatchEdit({ id, completed });
|
||||||
|
});
|
||||||
|
/** @type {ddeElementAddon<HTMLButtonElement>} */
|
||||||
|
const onDelete = on("click", () => dispatchDelete(id));
|
||||||
|
/** @type {ddeElementAddon<HTMLLabelElement>} */
|
||||||
|
const onStartEdit = on("dblclick", () => isEditing.set(true));
|
||||||
|
/** @type {ddeElementAddon<HTMLInputElement>} */
|
||||||
|
const onBlurEdit = on("blur", event => {
|
||||||
|
const value = /** @type {HTMLInputElement} */(event.target).value.trim();
|
||||||
|
if (value) {
|
||||||
|
dispatchEdit({ id, title: value });
|
||||||
|
} else {
|
||||||
|
dispatchDelete(id);
|
||||||
|
}
|
||||||
|
isEditing.set(false);
|
||||||
|
});
|
||||||
|
const formEdit = "edit";
|
||||||
|
/** @type {ddeElementAddon<HTMLFormElement>} */
|
||||||
|
const onSubmitEdit = on("submit", event => {
|
||||||
|
event.preventDefault();
|
||||||
|
const input = /** @type {HTMLFormElement} */(event.target).elements.namedItem(formEdit);
|
||||||
|
const value = /** @type {HTMLInputElement} */(input).value.trim();
|
||||||
|
if (value) {
|
||||||
|
dispatchEdit({ id, title: value });
|
||||||
|
} else {
|
||||||
|
dispatchDelete(id);
|
||||||
|
}
|
||||||
|
isEditing.set(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler for keyboard events in edit mode
|
||||||
|
* @type {ddeElementAddon<HTMLInputElement>}
|
||||||
|
*/
|
||||||
|
const onKeyDown = on("keydown", event => {
|
||||||
|
if (event.key !== "Escape") return;
|
||||||
|
isEditing.set(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return el("li", { classList: { completed: isCompleted, editing: isEditing } }).append(
|
||||||
|
el("div", { className: "view" }).append(
|
||||||
|
el("input", {
|
||||||
|
className: "toggle",
|
||||||
|
type: "checkbox",
|
||||||
|
checked: completed
|
||||||
|
}, onToggleCompleted),
|
||||||
|
el("label", { textContent: title }, onStartEdit),
|
||||||
|
el("button", { ariaLabel: "Delete todo", className: "destroy" }, onDelete)
|
||||||
|
),
|
||||||
|
S.el(isEditing, editing => !editing
|
||||||
|
? el()
|
||||||
|
: el("form", null, onSubmitEdit).append(
|
||||||
|
el("input", {
|
||||||
|
className: "edit",
|
||||||
|
name: formEdit,
|
||||||
|
value: title,
|
||||||
|
}, onBlurEdit, onKeyDown, addFocus)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the document head
|
||||||
|
document.head.append(
|
||||||
|
el("title", "TodoMVC: dd<el>"),
|
||||||
|
el("meta", { name: "description", content: "A TodoMVC implementation using dd<el>." }),
|
||||||
|
el("link", {
|
||||||
|
rel: "stylesheet",
|
||||||
|
href: "https://cdn.jsdelivr.net/npm/todomvc-common@1.0.5/base.css"
|
||||||
|
}),
|
||||||
|
el("link", {
|
||||||
|
rel: "stylesheet",
|
||||||
|
href: "https://cdn.jsdelivr.net/npm/todomvc-app-css@2.4.2/index.css"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set up the document body
|
||||||
|
document.body.append(
|
||||||
|
el(Todos),
|
||||||
|
el("footer", { className: "info" }).append(
|
||||||
|
el("p", "Double-click to edit a todo"),
|
||||||
|
el("p").append(
|
||||||
|
"Created with ",
|
||||||
|
el("a", { textContent: "deka-dom-el", href: "https://github.com/jaandrle/deka-dom-el" })
|
||||||
|
),
|
||||||
|
el("p").append(
|
||||||
|
"Part of ",
|
||||||
|
el("a", { textContent: "TodoMVC", href: "http://todomvc.com" })
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to set focus on an input element
|
||||||
|
* Uses requestAnimationFrame to ensure the element is rendered
|
||||||
|
* before trying to focus it
|
||||||
|
*
|
||||||
|
* @param {HTMLInputElement} editInput - The input element to focus
|
||||||
|
* @returns {number} The requestAnimationFrame ID
|
||||||
|
*/
|
||||||
|
function addFocus(editInput){
|
||||||
|
return requestAnimationFrame(()=> {
|
||||||
|
editInput.focus();
|
||||||
|
editInput.selectionStart = editInput.selectionEnd = editInput.value.length;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a signal for managing todos with persistence
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Loads todos from localStorage on initialization
|
||||||
|
* - Automatically saves todos to localStorage on changes
|
||||||
|
* - Provides actions for adding, editing, deleting todos
|
||||||
|
*/
|
||||||
|
function todosSignal(){
|
||||||
|
const store_key = "dde-todos";
|
||||||
|
// Try to load todos from localStorage
|
||||||
|
let savedTodos = [];
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(store_key);
|
||||||
|
if (stored) {
|
||||||
|
savedTodos = JSON.parse(stored);
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
const out= S(/** @type {Todo[]} */(savedTodos || []), {
|
||||||
|
/**
|
||||||
|
* Add a new todo
|
||||||
|
* @param {string} value - The title of the new todo
|
||||||
|
*/
|
||||||
|
add(value){
|
||||||
|
this.value.push({
|
||||||
|
completed: false,
|
||||||
|
title: value,
|
||||||
|
id: uuid(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Edit an existing todo
|
||||||
|
* @param {{ id: string, [key: string]: any }} data - Object containing id and fields to update
|
||||||
|
*/
|
||||||
|
edit({ id, ...update }){
|
||||||
|
const index = this.value.findIndex(t => t.id === id);
|
||||||
|
if (index === -1) return this.stopPropagation();
|
||||||
|
Object.assign(this.value[index], update);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Delete a todo by id
|
||||||
|
* @param {string} id - The id of the todo to delete
|
||||||
|
*/
|
||||||
|
delete(id){
|
||||||
|
const index = this.value.findIndex(t => t.id === id);
|
||||||
|
if (index === -1) return this.stopPropagation();
|
||||||
|
this.value.splice(index, 1);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Remove all completed todos
|
||||||
|
*/
|
||||||
|
clearCompleted() {
|
||||||
|
this.value = this.value.filter(todo => !todo.completed);
|
||||||
|
},
|
||||||
|
completeAll(state= true) {
|
||||||
|
this.value.forEach(todo => todo.completed = state);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Handle cleanup when signal is cleared
|
||||||
|
*/
|
||||||
|
[S.symbols.onclear](){
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save todos to localStorage whenever the signal changes
|
||||||
|
* @param {Todo[]} value - Current todos array
|
||||||
|
*/
|
||||||
|
S.on(out, /** @param {Todo[]} value */ function saveTodos(value) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(store_key, JSON.stringify(value));
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to save todos to localStorage", e);
|
||||||
|
// Optionally, provide user feedback
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a signal for managing route state
|
||||||
|
*
|
||||||
|
* @param {typeof S} signal - The signal constructor from a library
|
||||||
|
* @param {AbortSignal} abortSignal
|
||||||
|
*/
|
||||||
|
function routerSignal(signal, abortSignal){
|
||||||
|
const initial = location.hash.replace("#", "") || "all";
|
||||||
|
const out = signal(initial, {
|
||||||
|
/**
|
||||||
|
* Set the current route
|
||||||
|
* @param {"all"|"active"|"completed"} hash - The route to set
|
||||||
|
*/
|
||||||
|
set(hash){
|
||||||
|
location.hash = hash;
|
||||||
|
//this.value = hash;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup hash change listener
|
||||||
|
window.addEventListener("hashchange", () => {
|
||||||
|
const hash = location.hash.replace("#", "") || "all";
|
||||||
|
//S.action(out, "set", /** @type {"all"|"active"|"completed"} */(hash));
|
||||||
|
out.set(hash);
|
||||||
|
}, { signal: abortSignal });
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a RFC4122 version 4 compliant UUID
|
||||||
|
* Used to create unique identifiers for todo items
|
||||||
|
*
|
||||||
|
* @returns {string} A randomly generated UUID
|
||||||
|
*/
|
||||||
|
function uuid() {
|
||||||
|
let uuid = "";
|
||||||
|
for (let i = 0; i < 32; i++) {
|
||||||
|
let random = (Math.random() * 16) | 0;
|
||||||
|
|
||||||
|
if (i === 8 || i === 12 || i === 16 || i === 20)
|
||||||
|
uuid += "-";
|
||||||
|
|
||||||
|
uuid += (i === 12 ? 4 : i === 16 ? (random & 3) | 8 : random).toString(16);
|
||||||
|
}
|
||||||
|
return uuid;
|
||||||
|
}
|
@ -14,7 +14,7 @@ function component(){
|
|||||||
const textContent= S("Click to change text.");
|
const textContent= S("Click to change text.");
|
||||||
|
|
||||||
const onclickChange= on("click", function redispatch(){
|
const onclickChange= on("click", function redispatch(){
|
||||||
textContent("Text changed! "+(new Date()).toString())
|
textContent.set("Text changed! "+(new Date()).toString())
|
||||||
});
|
});
|
||||||
return el("p", textContent, onclickChange);
|
return el("p", textContent, onclickChange);
|
||||||
}
|
}
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import { scope } from "deka-dom-el";
|
|
||||||
import { S } from "deka-dom-el/signals";
|
|
||||||
|
|
||||||
function customSignalLogic() {
|
|
||||||
// Create an isolated scope for a specific operation
|
|
||||||
scope.push(); // Start new scope
|
|
||||||
|
|
||||||
// These signals are in the new scope
|
|
||||||
const isolatedCount = S(0);
|
|
||||||
const isolatedDerived = S(() => isolatedCount.get() * 2);
|
|
||||||
|
|
||||||
// Clean up by returning to previous scope
|
|
||||||
scope.pop();
|
|
||||||
}
|
|
@ -16,7 +16,9 @@ function Counter() {
|
|||||||
// THE HOST IS PROBABLY DIFFERENT THAN
|
// THE HOST IS PROBABLY DIFFERENT THAN
|
||||||
// YOU EXPECT AND SIGNAL MAY BE
|
// YOU EXPECT AND SIGNAL MAY BE
|
||||||
// UNEXPECTEDLY REMOVED!!!
|
// UNEXPECTEDLY REMOVED!!!
|
||||||
host().querySelector("button").disabled = count.get() >= 10;
|
S.on(count, (count)=>
|
||||||
|
host().querySelector("button").disabled = count >= 10
|
||||||
|
);
|
||||||
};
|
};
|
||||||
setTimeout(()=> {
|
setTimeout(()=> {
|
||||||
// ok, BUT consider extract to separate function
|
// ok, BUT consider extract to separate function
|
||||||
|
@ -7,7 +7,7 @@ const todos= S([], {
|
|||||||
const removed= this.value.pop();
|
const removed= this.value.pop();
|
||||||
if(removed) S.clear(removed);
|
if(removed) S.clear(removed);
|
||||||
},
|
},
|
||||||
[S.symbols.onclear](){ // this covers `O.clear(todos)`
|
[S.symbols.onclear](){ // this covers `S.clear(todos)`
|
||||||
S.clear(...this.value);
|
S.clear(...this.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
// Handling async data in SSR
|
// Handling async data in SSR
|
||||||
import { JSDOM } from "jsdom";
|
import { JSDOM } from "jsdom";
|
||||||
import { S } from "deka-dom-el/signals";
|
|
||||||
import { register, queue } from "deka-dom-el/jsdom";
|
import { register, queue } from "deka-dom-el/jsdom";
|
||||||
|
|
||||||
async function renderWithAsyncData() {
|
async function renderWithAsyncData() {
|
||||||
@ -8,6 +7,27 @@ async function renderWithAsyncData() {
|
|||||||
const { el } = await register(dom);
|
const { el } = await register(dom);
|
||||||
|
|
||||||
// Create a component that fetches data
|
// Create a component that fetches data
|
||||||
|
const { AsyncComponent } = await import("./components/AsyncComponent.js");
|
||||||
|
|
||||||
|
// Render the page
|
||||||
|
dom.window.document.body.append(
|
||||||
|
el("h1", "Page with Async Data"),
|
||||||
|
el(AsyncComponent)
|
||||||
|
);
|
||||||
|
|
||||||
|
// IMPORTANT: Wait for all queued operations to complete
|
||||||
|
await queue();
|
||||||
|
|
||||||
|
// Now the HTML includes all async content
|
||||||
|
const html = dom.serialize();
|
||||||
|
console.log(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderWithAsyncData();
|
||||||
|
|
||||||
|
// file: components/AsyncComponent.js
|
||||||
|
import { el } from "deka-dom-el";
|
||||||
|
import { S } from "deka-dom-el/signals";
|
||||||
function AsyncComponent() {
|
function AsyncComponent() {
|
||||||
const title= S("-");
|
const title= S("-");
|
||||||
const description= S("-");
|
const description= S("-");
|
||||||
@ -25,19 +45,3 @@ async function renderWithAsyncData() {
|
|||||||
el("p", description)
|
el("p", description)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the page
|
|
||||||
dom.window.document.body.append(
|
|
||||||
el("h1", "Page with Async Data"),
|
|
||||||
el(AsyncComponent)
|
|
||||||
);
|
|
||||||
|
|
||||||
// IMPORTANT: Wait for all queued operations to complete
|
|
||||||
await queue();
|
|
||||||
|
|
||||||
// Now the HTML includes all async content
|
|
||||||
const html = dom.serialize();
|
|
||||||
console.log(html);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderWithAsyncData();
|
|
||||||
|
@ -11,6 +11,7 @@ async function renderPage() {
|
|||||||
const { el } = await register(dom);
|
const { el } = await register(dom);
|
||||||
|
|
||||||
// Create a simple header component
|
// Create a simple header component
|
||||||
|
// can be separated into a separate file and use `import { el } from "deka-dom-el"`
|
||||||
function Header({ title }) {
|
function Header({ title }) {
|
||||||
return el("header").append(
|
return el("header").append(
|
||||||
el("h1", title),
|
el("h1", title),
|
||||||
|
@ -17,6 +17,7 @@ async function renderPage() {
|
|||||||
const { el } = await register(dom);
|
const { el } = await register(dom);
|
||||||
|
|
||||||
// 4. Dynamically import page components
|
// 4. Dynamically import page components
|
||||||
|
// use `import { el } from "deka-dom-el"`
|
||||||
const { Header } = await import("./components/Header.js");
|
const { Header } = await import("./components/Header.js");
|
||||||
const { Content } = await import("./components/Content.js");
|
const { Content } = await import("./components/Content.js");
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Basic jsdom integration example
|
// Basic jsdom integration example
|
||||||
import { JSDOM } from "jsdom";
|
import { JSDOM } from "jsdom";
|
||||||
import { register, unregister, queue } from "deka-dom-el/jsdom.js";
|
import { register, unregister, queue } from "deka-dom-el/jsdom";
|
||||||
|
|
||||||
// Create a jsdom instance
|
// Create a jsdom instance
|
||||||
const dom = new JSDOM("<!DOCTYPE html><html><body></body></html>");
|
const dom = new JSDOM("<!DOCTYPE html><html><body></body></html>");
|
||||||
|
@ -12,7 +12,7 @@ async function buildSite() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Create output directory
|
// Create output directory
|
||||||
mkdirSync("./dist", { recursive: true });
|
mkdirSync("./dist/docs", { recursive: true });
|
||||||
|
|
||||||
// Build each page
|
// Build each page
|
||||||
for (const page of pages) {
|
for (const page of pages) {
|
||||||
@ -23,6 +23,7 @@ async function buildSite() {
|
|||||||
const { el } = await register(dom);
|
const { el } = await register(dom);
|
||||||
|
|
||||||
// Import the page component
|
// Import the page component
|
||||||
|
// use `import { el } from "deka-dom-el"`
|
||||||
const { default: PageComponent } = await import(page.component);
|
const { default: PageComponent } = await import(page.component);
|
||||||
|
|
||||||
// Render the page with its metadata
|
// Render the page with its metadata
|
||||||
@ -35,7 +36,7 @@ async function buildSite() {
|
|||||||
|
|
||||||
// Write the HTML to a file
|
// Write the HTML to a file
|
||||||
const html = dom.serialize();
|
const html = dom.serialize();
|
||||||
writeFileSync(`./dist/${page.id}.html`, html);
|
writeFileSync(`./dist/docs/${page.id}.html`, html);
|
||||||
|
|
||||||
console.log(`Built page: ${page.id}.html`);
|
console.log(`Built page: ${page.id}.html`);
|
||||||
}
|
}
|
||||||
|
83
docs/components/getLibraryUrl.html.js
Normal file
83
docs/components/getLibraryUrl.html.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { styles, page_id } from "../ssr.js";
|
||||||
|
|
||||||
|
styles.css`
|
||||||
|
#library-url-form {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background-color: var(--bg-sidebar);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#library-url-form .selectors {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#library-url-form output {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#library-url-form output p {
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 0.25rem 0;
|
||||||
|
color: var(--text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
#library-url-form .url-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: -0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#library-url-form .url-title strong {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#library-url-form .url-title span {
|
||||||
|
color: var(--text-light);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#library-url-form .code {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#library-url-form .info-text {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-style: italic;
|
||||||
|
margin-top: 1rem;
|
||||||
|
color: var(--text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
#library-url-form .selectors {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#library-url-form select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
import { el } from "deka-dom-el";
|
||||||
|
import { ireland } from "./ireland.html.js";
|
||||||
|
|
||||||
|
export function getLibraryUrl(){
|
||||||
|
return el(ireland, {
|
||||||
|
src: new URL("./getLibraryUrl.js.js", import.meta.url),
|
||||||
|
exportName: "getLibraryUrl",
|
||||||
|
page_id,
|
||||||
|
});
|
||||||
|
}
|
92
docs/components/getLibraryUrl.js.js
Normal file
92
docs/components/getLibraryUrl.js.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { el, on } from "deka-dom-el";
|
||||||
|
import { S } from "deka-dom-el/signals";
|
||||||
|
|
||||||
|
const url_base= {
|
||||||
|
jsdeka: "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getLibraryUrl(){
|
||||||
|
const lib= S([ "esm", "-with-signals", ".min" ]);
|
||||||
|
const url= S(()=> url_base.jsdeka+lib.get().join(""));
|
||||||
|
const urlLabel= S(() => {
|
||||||
|
const [format, signalsPart, minified] = lib.get();
|
||||||
|
const formatText = format === "esm" ? "ES Module" : "IIFE";
|
||||||
|
const signalsText = signalsPart ? " with signals" : "";
|
||||||
|
const minText = minified ? " (minified)" : "";
|
||||||
|
return `${formatText}${signalsText}${minText}`;
|
||||||
|
})
|
||||||
|
const onSubmit= on("submit", ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
const form= new FormData(/** @type {HTMLFormElement} */ (ev.target));
|
||||||
|
lib.set([
|
||||||
|
"module",
|
||||||
|
"what",
|
||||||
|
"minified",
|
||||||
|
].map(name => /** @type {string} */(form.get(name))));
|
||||||
|
});
|
||||||
|
const onChangeSubmit= on("change",
|
||||||
|
ev=> /** @type {HTMLSelectElement} */(ev.target).form.requestSubmit()
|
||||||
|
);
|
||||||
|
|
||||||
|
return el("form", { id: "library-url-form" }, onSubmit).append(
|
||||||
|
el("h4", "Select your preferred library format:"),
|
||||||
|
el("div", { className: "selectors" }).append(
|
||||||
|
el("select", { name: "module" }, onChangeSubmit,
|
||||||
|
on.defer(select => select.value = lib.get()[0]),
|
||||||
|
).append(
|
||||||
|
el("option", { value: "esm", textContent: "ESM — modern JavaScript module" }),
|
||||||
|
el("option", { value: "iife", textContent: "IIFE — legacy JavaScript with DDE global variable" }),
|
||||||
|
),
|
||||||
|
el("select", { name: "what" }, onChangeSubmit,
|
||||||
|
on.defer(select => select.value = lib.get()[1]),
|
||||||
|
).append(
|
||||||
|
el("option", { value: "", textContent: "DOM part only" }),
|
||||||
|
el("option", { value: "-with-signals", textContent: "DOM + signals" }),
|
||||||
|
),
|
||||||
|
el("select", { name: "minified" }, onChangeSubmit,
|
||||||
|
on.defer(select => select.value = lib.get()[2]),
|
||||||
|
).append(
|
||||||
|
el("option", { value: "", textContent: "Unminified" }),
|
||||||
|
el("option", { value: ".min", textContent: "Minified" }),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
el("output").append(
|
||||||
|
el("div", { className: "url-title" }).append(
|
||||||
|
el("strong", "JavaScript:"),
|
||||||
|
el("span", urlLabel),
|
||||||
|
),
|
||||||
|
el(code, { value: S(()=> url.get()+".js") }),
|
||||||
|
el("div", { className: "url-title" }).append(
|
||||||
|
el("strong", "TypeScript definition:")
|
||||||
|
),
|
||||||
|
el(code, { value: S(()=> url.get()+".d.ts") }),
|
||||||
|
el("p", { className: "info-text",
|
||||||
|
textContent: "Use the CDN URL in your HTML or import it in your JavaScript files."
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/** @param {{ value: ddeSignal<string> }} props */
|
||||||
|
function code({ value }){
|
||||||
|
/** @type {ddeSignal<"Copy"|"Copied!">} */
|
||||||
|
const textContent= S("Copy");
|
||||||
|
const onCopy= on("click", () => {
|
||||||
|
navigator.clipboard.writeText(value.get());
|
||||||
|
|
||||||
|
textContent.set("Copied!");
|
||||||
|
setTimeout(() => {
|
||||||
|
textContent.set("Copy");
|
||||||
|
}, 1500);
|
||||||
|
});
|
||||||
|
return el("div", { className: "code", dataJs: "done", tabIndex: 0 }).append(
|
||||||
|
el("code").append(
|
||||||
|
el("pre", value),
|
||||||
|
),
|
||||||
|
el("button", {
|
||||||
|
className: "copy-button",
|
||||||
|
textContent,
|
||||||
|
ariaLabel: "Copy code to clipboard",
|
||||||
|
}, onCopy)
|
||||||
|
)
|
||||||
|
;
|
||||||
|
}
|
@ -1,3 +1,33 @@
|
|||||||
|
import { styles } from "../ssr.js";
|
||||||
|
styles.css`
|
||||||
|
[data-dde-mark] {
|
||||||
|
opacity: .5;
|
||||||
|
filter: grayscale();
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
animation: fadein 2s infinite ease forwards;;
|
||||||
|
}
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
&::after {
|
||||||
|
content: "Loading Ireland…";
|
||||||
|
background-color: rgba(0, 0, 0, .5);
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
position: absolute;
|
||||||
|
top: 3%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes fadein {
|
||||||
|
from { opacity: .5; }
|
||||||
|
to { opacity: .85; }
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
import { el, queue } from "deka-dom-el";
|
import { el, queue } from "deka-dom-el";
|
||||||
import { addEventListener, registerClientFile } from "../ssr.js";
|
import { addEventListener, registerClientFile } from "../ssr.js";
|
||||||
import { relative } from "node:path";
|
import { relative } from "node:path";
|
||||||
@ -13,7 +43,6 @@ const componentsRegistry = new Map();
|
|||||||
* @param {object} attrs
|
* @param {object} attrs
|
||||||
* @param {URL} attrs.src - Path to the file containing the component
|
* @param {URL} attrs.src - Path to the file containing the component
|
||||||
* @param {string} [attrs.exportName="default"] - Name of the export to use
|
* @param {string} [attrs.exportName="default"] - Name of the export to use
|
||||||
* @param {string} attrs.page_id - ID of the current page
|
|
||||||
* @param {object} [attrs.props={}] - Props to pass to the component
|
* @param {object} [attrs.props={}] - Props to pass to the component
|
||||||
*/
|
*/
|
||||||
export function ireland({ src, exportName = "default", props = {} }) {
|
export function ireland({ src, exportName = "default", props = {} }) {
|
||||||
@ -21,10 +50,17 @@ export function ireland({ src, exportName = "default", props = {} }) {
|
|||||||
const path= "./"+relative(dir, src.pathname);
|
const path= "./"+relative(dir, src.pathname);
|
||||||
const id = "ireland-" + generateComponentId(src);
|
const id = "ireland-" + generateComponentId(src);
|
||||||
const element = el.mark({ type: "later", name: ireland.name });
|
const element = el.mark({ type: "later", name: ireland.name });
|
||||||
queue(import(path).then(module => {
|
queue(
|
||||||
|
import(path)
|
||||||
|
.then(module => {
|
||||||
const component = module[exportName];
|
const component = module[exportName];
|
||||||
element.replaceWith(el(component, props, mark(id)));
|
const content= el(component, props, mark(id));
|
||||||
}));
|
element.replaceWith(content);
|
||||||
|
content.querySelectorAll("input, textarea, button")
|
||||||
|
.forEach(el=> el.disabled= true);
|
||||||
|
})
|
||||||
|
.catch(console.error)
|
||||||
|
);
|
||||||
|
|
||||||
if(!componentsRegistry.size)
|
if(!componentsRegistry.size)
|
||||||
addEventListener("oneachrender", registerClientPart);
|
addEventListener("oneachrender", registerClientPart);
|
||||||
|
@ -11,10 +11,6 @@ export function mnemonic(){
|
|||||||
el("code", "customElementWithDDE(<custom-element>)"),
|
el("code", "customElementWithDDE(<custom-element>)"),
|
||||||
" — register <custom-element> to DDE library, see also `lifecyclesToEvents`, can be also used as decorator",
|
" — register <custom-element> to DDE library, see also `lifecyclesToEvents`, can be also used as decorator",
|
||||||
),
|
),
|
||||||
el("li").append(
|
|
||||||
el("code", "observedAttributes(<custom-element>)"),
|
|
||||||
" — returns record of observed attributes (keys uses camelCase)",
|
|
||||||
),
|
|
||||||
el("li").append(
|
el("li").append(
|
||||||
el("code", "S.observedAttributes(<custom-element>)"),
|
el("code", "S.observedAttributes(<custom-element>)"),
|
||||||
" — returns record of observed attributes (keys uses camelCase and values are signals)",
|
" — returns record of observed attributes (keys uses camelCase and values are signals)",
|
||||||
|
@ -9,7 +9,7 @@ export function mnemonic(){
|
|||||||
),
|
),
|
||||||
el("li").append(
|
el("li").append(
|
||||||
el("code", "el(<tag-name>, <primitive>)[.append(...)]: <element-from-tag-name>"),
|
el("code", "el(<tag-name>, <primitive>)[.append(...)]: <element-from-tag-name>"),
|
||||||
" — simple element containing only text",
|
" — simple element containing only text (accepts string, number or signal)",
|
||||||
),
|
),
|
||||||
el("li").append(
|
el("li").append(
|
||||||
el("code", "el(<tag-name>, <object>)[.append(...)]: <element-from-tag-name>"),
|
el("code", "el(<tag-name>, <object>)[.append(...)]: <element-from-tag-name>"),
|
||||||
@ -26,6 +26,6 @@ export function mnemonic(){
|
|||||||
el("li").append(
|
el("li").append(
|
||||||
el("code", "elNS(<namespace>)(<as-el-see-above>)[.append(...)]: <element-based-on-arguments>"),
|
el("code", "elNS(<namespace>)(<as-el-see-above>)[.append(...)]: <element-based-on-arguments>"),
|
||||||
" — typically SVG elements",
|
" — typically SVG elements",
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -13,18 +13,23 @@ export function mnemonic(){
|
|||||||
". To connect to custom element see following page, else it is simulated by MutationObserver."
|
". To connect to custom element see following page, else it is simulated by MutationObserver."
|
||||||
),
|
),
|
||||||
el("li").append(
|
el("li").append(
|
||||||
el("code", "dispatchEvent(<event>[, <options>])(element)"),
|
el("code", "on.defer(<identity>=> <identity>)(<identity>)"),
|
||||||
" — just ", el("code", "<element>.dispatchEvent(new Event(<event>[, <options>]))")
|
" — calls callback later",
|
||||||
),
|
),
|
||||||
el("li").append(
|
el("li").append(
|
||||||
el("code", "dispatchEvent(<event>, <element>)([<detail>])"),
|
el("code", "dispatchEvent(<event>[, <options>])(element)"),
|
||||||
" — just ", el("code", "<element>.dispatchEvent(new Event(<event>))"), " or ",
|
" — just ", el("code", "<element>.dispatchEvent(new Event(<event>[, <options>]))")
|
||||||
el("code", "<element>.dispatchEvent(new CustomEvent(<event>, { detail: <detail> }))")
|
|
||||||
),
|
),
|
||||||
el("li").append(
|
el("li").append(
|
||||||
el("code", "dispatchEvent(<event>[, <options>])(<element>[, <detail>])"),
|
el("code", "dispatchEvent(<event>[, <options>])(<element>[, <detail>])"),
|
||||||
" — just ", el("code", "<element>.dispatchEvent(new Event(<event>[, <options>] ))"), " or ",
|
" — just ", el("code", "<element>.dispatchEvent(new Event(<event>[, <options>] ))"), " or ",
|
||||||
el("code", "<element>.dispatchEvent(new CustomEvent(<event>, { detail: <detail> }))")
|
el("code", "<element>.dispatchEvent(new CustomEvent(<event>, { detail: <detail> }))")
|
||||||
),
|
),
|
||||||
|
el("li").append(
|
||||||
|
el("code", "dispatchEvent(<event>[, <options>], <host>)([<detail>])"),
|
||||||
|
" — just ", el("code", "<host>().dispatchEvent(new Event(<event>[, <options>]))"), " or ",
|
||||||
|
el("code", "<host>().dispatchEvent(new CustomEvent(<event>, { detail: <detail> }[, <options>] ))"),
|
||||||
|
" (see scopes section of docs)"
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
15
docs/components/mnemonic/optimization-init.js
Normal file
15
docs/components/mnemonic/optimization-init.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { el } from "deka-dom-el";
|
||||||
|
import { mnemonicUl } from "../mnemonicUl.html.js";
|
||||||
|
|
||||||
|
export function mnemonic(){
|
||||||
|
return mnemonicUl().append(
|
||||||
|
el("li").append(
|
||||||
|
el("code", "memo.scope(<function>, <argument(s)>)"),
|
||||||
|
" — Scope for memo",
|
||||||
|
),
|
||||||
|
el("li").append(
|
||||||
|
el("code", "memo(<key>, <generator>)"),
|
||||||
|
" — returns value from memo and/or generates it (and caches it)",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
@ -14,6 +14,10 @@ export function mnemonic(){
|
|||||||
el("li").append(
|
el("li").append(
|
||||||
el("code", "scope.host(...<addons>)"),
|
el("code", "scope.host(...<addons>)"),
|
||||||
" — use addons to current component",
|
" — use addons to current component",
|
||||||
|
),
|
||||||
|
el("li").append(
|
||||||
|
el("code", "scope.signal"),
|
||||||
|
" — get AbortSignal that triggers when the element disconnects",
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -14,10 +14,6 @@ export function mnemonic(){
|
|||||||
el("code", "S.on(<signal>, <listener>[, <options>])"),
|
el("code", "S.on(<signal>, <listener>[, <options>])"),
|
||||||
" — listen to the signal value changes",
|
" — listen to the signal value changes",
|
||||||
),
|
),
|
||||||
el("li").append(
|
|
||||||
el("code", "S.clear(...<signals>)"),
|
|
||||||
" — off and clear signals",
|
|
||||||
),
|
|
||||||
el("li").append(
|
el("li").append(
|
||||||
el("code", "S(<value>, <actions>)"),
|
el("code", "S(<value>, <actions>)"),
|
||||||
" — signal: pattern to create complex reactive objects/arrays",
|
" — signal: pattern to create complex reactive objects/arrays",
|
||||||
@ -29,6 +25,11 @@ export function mnemonic(){
|
|||||||
el("li").append(
|
el("li").append(
|
||||||
el("code", "S.el(<signal>, <function-returning-dom>)"),
|
el("code", "S.el(<signal>, <function-returning-dom>)"),
|
||||||
" — render partial dom structure (template) based on the current signal value",
|
" — render partial dom structure (template) based on the current signal value",
|
||||||
)
|
),
|
||||||
|
el("li").append(
|
||||||
|
el("code", "S.clear(...<signals>)"),
|
||||||
|
" — off and clear signals (most of the time it is not needed as reactive ",
|
||||||
|
"attributes and elements are handled automatically)",
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -76,11 +76,9 @@ export function h3({ textContent, id }){
|
|||||||
el("a", {
|
el("a", {
|
||||||
className: "heading-anchor",
|
className: "heading-anchor",
|
||||||
href: "#"+id,
|
href: "#"+id,
|
||||||
textContent: "#",
|
|
||||||
title: `Link to this section: ${textContent}`,
|
title: `Link to this section: ${textContent}`,
|
||||||
"aria-label": `Link to section ${textContent}`
|
|
||||||
}),
|
}),
|
||||||
" ",
|
"# ",
|
||||||
textContent,
|
textContent,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
50
docs/components/scrollTop.html.js
Normal file
50
docs/components/scrollTop.html.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { styles } from "../ssr.js";
|
||||||
|
|
||||||
|
styles.css`
|
||||||
|
/* Scroll to top button */
|
||||||
|
.scroll-top-button {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 2rem;
|
||||||
|
left: 2rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--primary);
|
||||||
|
color: var(--button-text);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
text-decoration: none;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
transition: background-color 0.2s ease, transform 0.2s ease;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-top-button:hover {
|
||||||
|
background-color: var(--primary-dark);
|
||||||
|
transform: translateY(-4px);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.scroll-top-button {
|
||||||
|
bottom: 0.5rem;
|
||||||
|
left: unset;
|
||||||
|
right: .5rem;
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
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: "*",
|
||||||
|
});
|
||||||
|
}
|
14
docs/components/scrollTop.js.js
Normal file
14
docs/components/scrollTop.js.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { el } from "deka-dom-el";
|
||||||
|
|
||||||
|
export function scrollTop() {
|
||||||
|
return el("a", {
|
||||||
|
href: "#",
|
||||||
|
className: "scroll-top-button",
|
||||||
|
ariaLabel: "Scroll to top",
|
||||||
|
textContent: "↑",
|
||||||
|
onclick: (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -16,7 +16,6 @@ styles.css`
|
|||||||
SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
|
SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
|
||||||
|
|
||||||
--body-max-width: 40rem;
|
--body-max-width: 40rem;
|
||||||
--sidebar-width: 20rem;
|
|
||||||
--header-height: 4rem;
|
--header-height: 4rem;
|
||||||
--border-radius: 0.375rem;
|
--border-radius: 0.375rem;
|
||||||
|
|
||||||
@ -73,6 +72,7 @@ styles.css`
|
|||||||
|
|
||||||
html {
|
html {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
|
tab-size: var(--tab-size, 2rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Accessibility improvements */
|
/* Accessibility improvements */
|
||||||
@ -86,7 +86,7 @@ html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:focus-visible {
|
:focus-visible {
|
||||||
outline: 3px solid hsl(231, 48%, 70%);
|
outline: 3px solid var(--primary-light);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,6 +193,34 @@ pre code {
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
figure {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-light);
|
||||||
|
border: 1px dashed var(--border);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
|
||||||
|
img {
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background-color: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-family: var(--font-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
select:hover {
|
||||||
|
border-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
/* Layout */
|
/* Layout */
|
||||||
body {
|
body {
|
||||||
@ -215,8 +243,8 @@ body {
|
|||||||
}
|
}
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
body {
|
body {
|
||||||
grid-template-rows: var(--header-height) 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
grid-template-columns: var(--sidebar-width) 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"header header"
|
"header header"
|
||||||
"sidebar content";
|
"sidebar content";
|
||||||
@ -234,7 +262,7 @@ body > main {
|
|||||||
}
|
}
|
||||||
body > main > *, body > main slot > * {
|
body > main > *, body > main slot > * {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: calc(var(--body-max-width) * 5/3);
|
||||||
margin-inline: auto;
|
margin-inline: auto;
|
||||||
grid-column: main;
|
grid-column: main;
|
||||||
}
|
}
|
||||||
@ -250,7 +278,7 @@ h2 {
|
|||||||
|
|
||||||
/* Section headings with better visual hierarchy */
|
/* Section headings with better visual hierarchy */
|
||||||
body > main h3, body > main h4 {
|
body > main h3, body > main h4 {
|
||||||
scroll-margin-top: calc(var(--header-height) + 1rem);
|
scroll-margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make clickable heading links for better navigation */
|
/* Make clickable heading links for better navigation */
|
||||||
@ -267,9 +295,8 @@ body > main h3, body > main h4 {
|
|||||||
/* Boxes */
|
/* Boxes */
|
||||||
.illustration{
|
.illustration{
|
||||||
grid-column: full-main;
|
grid-column: full-main;
|
||||||
width: calc(100% - .75em);
|
|
||||||
}
|
}
|
||||||
.illustration:not(:has( .comparison)){
|
.illustration:not(:has( .comparison)):not(:has( .tabs)) {
|
||||||
grid-column: main;
|
grid-column: main;
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
|
import "./components/getLibraryUrl.html.js";
|
||||||
import { t, T } from "./utils/index.js";
|
import { t, T } from "./utils/index.js";
|
||||||
export const info= {
|
export const info= {
|
||||||
href: "./",
|
href: "./",
|
||||||
title: t`Introduction`,
|
title: t`Introduction`,
|
||||||
fullTitle: t`Vanilla for flavouring — a full-fledged feast for large projects`,
|
fullTitle: t`Vanilla for flavouring — a full-fledged feast for large projects`,
|
||||||
description: t`A lightweight, reactive DOM library for creating dynamic UIs with a declarative syntax`,
|
description: t`Reactive DOM library for creating dynamic UIs with a declarative syntax`,
|
||||||
};
|
};
|
||||||
|
|
||||||
import { el } from "deka-dom-el";
|
import { el } from "deka-dom-el";
|
||||||
@ -11,9 +12,14 @@ import { simplePage } from "./layout/simplePage.html.js";
|
|||||||
import { h3 } from "./components/pageUtils.html.js";
|
import { h3 } from "./components/pageUtils.html.js";
|
||||||
import { example } from "./components/example.html.js";
|
import { example } from "./components/example.html.js";
|
||||||
import { code } from "./components/code.html.js";
|
import { code } from "./components/code.html.js";
|
||||||
|
import { getLibraryUrl } from "./components/getLibraryUrl.html.js";
|
||||||
/** @param {string} url */
|
/** @param {string} url */
|
||||||
const fileURL= url=> new URL(url, import.meta.url);
|
const fileURL= url=> new URL(url, import.meta.url);
|
||||||
const references= {
|
const references= {
|
||||||
|
npm: {
|
||||||
|
title: t`NPM package page for dd<el>`,
|
||||||
|
href: "https://www.npmjs.com/package/deka-dom-el",
|
||||||
|
},
|
||||||
w_mvv: {
|
w_mvv: {
|
||||||
title: t`Wikipedia: Model–view–viewmodel`,
|
title: t`Wikipedia: Model–view–viewmodel`,
|
||||||
href: "https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel",
|
href: "https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel",
|
||||||
@ -25,28 +31,32 @@ const references= {
|
|||||||
};
|
};
|
||||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||||
export function page({ pkg, info }){
|
export function page({ pkg, info }){
|
||||||
const page_id= info.id;
|
|
||||||
return el(simplePage, { info, pkg }).append(
|
return el(simplePage, { info, pkg }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Welcome to Deka DOM Elements (DDE) — a lightweight library for building dynamic UIs with a declarative
|
Welcome to Deka DOM Elements (dd<el> or DDE) — a library for building dynamic UIs with
|
||||||
syntax that stays close to the native DOM API. DDE gives you powerful reactive tools without the complexity
|
a declarative syntax that stays close to the native DOM API. dd<el> gives you powerful reactive tools
|
||||||
and overhead of larger frameworks.
|
without the complexity and overhead of larger frameworks.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "callout" }).append(
|
el("div", { className: "callout" }).append(
|
||||||
el("h4", t`What Makes DDE Special`),
|
el("h4", t`Key Benefits of dd<el>`),
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
el("li", t`No build step required — use directly in the browser`),
|
el("li", t`No build step required — use directly in the browser`),
|
||||||
el("li", t`Lightweight core (~10–15kB minified) with zero dependencies`),
|
el("li", t`Minimalized footprint:`),
|
||||||
|
el("ul").append(
|
||||||
|
el("li", t`lightweight core (~10–15kB minified)`),
|
||||||
|
el("li", t`…without unnecessary dependencies (0 at now 😇)`),
|
||||||
|
el("li", t`auto-releasing resources with focus on performance and development experience`),
|
||||||
|
),
|
||||||
el("li", t`Natural DOM API — work with real DOM nodes, not abstractions`),
|
el("li", t`Natural DOM API — work with real DOM nodes, not abstractions`),
|
||||||
el("li", t`Built-in reactivity with powerful signals system`),
|
el("li", t`Built-in (but optional) reactivity with simplified but powerful signals system`),
|
||||||
el("li", t`Clean code organization with the 3PS pattern`)
|
el("li", t`Clean code organization with the 3PS pattern`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/introducing/helloWorld.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/introducing/helloWorld.js") }),
|
||||||
|
|
||||||
el(h3, { textContent: t`The 3PS Pattern: A Better Way to Build UIs`, id: "h-3ps" }),
|
el(h3, { textContent: t`The 3PS Pattern: Simplified architecture pattern`, id: "h-3ps" }),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
At the heart of DDE is the 3PS (3-Part Separation) pattern. This simple yet powerful approach helps you
|
At the heart of dd<el> is the 3PS (3-Part Separation) pattern. This simple yet powerful approach helps you
|
||||||
organize your UI code into three distinct areas, making your applications more maintainable and easier
|
organize your UI code into three distinct areas, making your applications more maintainable and easier
|
||||||
to reason about.
|
to reason about.
|
||||||
`),
|
`),
|
||||||
@ -54,63 +64,116 @@ export function page({ pkg, info }){
|
|||||||
el("div", { className: "tabs" }).append(
|
el("div", { className: "tabs" }).append(
|
||||||
el("div", { className: "tab" }).append(
|
el("div", { className: "tab" }).append(
|
||||||
el("h5", t`Traditional DOM Manipulation`),
|
el("h5", t`Traditional DOM Manipulation`),
|
||||||
el(code, { src: fileURL("./components/examples/introducing/3ps-before.js"), page_id }),
|
el(code, { src: fileURL("./components/examples/introducing/3ps-before.js") }),
|
||||||
),
|
),
|
||||||
el("div", { className: "tab" }).append(
|
el("div", { className: "tab" }).append(
|
||||||
el("h5", t`DDE's 3PS Pattern`),
|
el("h5", t`dd<el>'s 3PS Pattern`),
|
||||||
el(code, { src: fileURL("./components/examples/introducing/3ps.js"), page_id }),
|
el(code, { src: fileURL("./components/examples/introducing/3ps.js") }),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
The 3PS pattern separates your code into three clear parts:
|
The 3PS pattern separates your code into three clear parts:
|
||||||
`),
|
`),
|
||||||
el("ol").append(
|
el("ol").append(
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Create State")}: Define your application's reactive data using signals
|
${el("strong", "Create State")}: Define your application’s reactive data using signals
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Bind to Elements")}: Define how UI elements react to state changes
|
${el("strong", "React to Changes")}: Define how UI elements and other parts of your app react to state
|
||||||
|
changes
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Update State")}: Modify state in response to user events or other triggers
|
${el("strong", "Update State")}: Modify state in response to user events or other triggers
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
|
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
By separating these concerns, your code becomes more modular, testable, and easier to maintain. This
|
By separating these concerns, your code becomes more modular, testable, and easier to maintain. This
|
||||||
approach shares principles with more formal patterns like ${el("a", { textContent: "MVVM",
|
approach ${el("strong", "is not something new and/or special to dd<el>")}. It’s based on ${el("a", {
|
||||||
...references.w_mvv })} and ${el("a", { textContent: "MVC", ...references.w_mvc })}, but with less
|
textContent: "MVC", ...references.w_mvc })} (${el("a", { textContent: "MVVM", ...references.w_mvv })}),
|
||||||
overhead and complexity.
|
but is there presented in simpler form.
|
||||||
`),
|
`),
|
||||||
|
|
||||||
el("div", { className: "note" }).append(
|
el("div", { className: "note" }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
The 3PS pattern becomes especially powerful when combined with components, allowing you to create
|
The 3PS pattern becomes especially powerful when combined with components, allowing you to create
|
||||||
reusable pieces of UI with encapsulated state and behavior. You'll learn more about this in the
|
reusable pieces of UI with encapsulated state and behavior. You’ll learn more about this in the
|
||||||
following sections.
|
following sections.
|
||||||
|
`),
|
||||||
|
el("p").append(T`
|
||||||
|
The 3PS pattern isn’t required to use with dd<el> but it is good practice to follow it or some similar
|
||||||
|
software architecture.
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`How to Use This Documentation`),
|
el(h3, t`Getting Started`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
This guide will take you through DDE's features step by step:
|
There are multiple ways to include dd<el> in your project. You can use npm for a full development setup,
|
||||||
|
or directly include it from a CDN for quick prototyping.
|
||||||
`),
|
`),
|
||||||
el("ol").append(
|
el("h4", "npm installation"),
|
||||||
el("li").append(...T`${el("strong", "Elements")} — Creating and manipulating DOM elements`),
|
el(code, { content: "npm install deka-dom-el --save", language: "shell" }),
|
||||||
el("li").append(...T`${el("strong", "Events")} — Handling user interactions and lifecycle events`),
|
el("p").append(T`
|
||||||
el("li").append(...T`${el("strong", "Signals")} — Adding reactivity to your UI`),
|
…see ${el("a", { textContent: "package page", ...references.npm, target: "_blank" })}.
|
||||||
el("li").append(...T`${el("strong", "Scopes")} — Managing component lifecycles`),
|
`),
|
||||||
el("li").append(...T`${el("strong", "Custom Elements")} — Building web components`),
|
|
||||||
el("li").append(...T`${el("strong", "Debugging")} — Tools to help you build and fix your apps`),
|
el("h4", "CDN / Direct Script Usage"),
|
||||||
el("li").append(...T`${el("strong", "Extensions")} — Integrating third-party functionalities`),
|
el("p").append(T`
|
||||||
el("li").append(...T`${el("strong", "Ireland Components")} —
|
Use the interactive selector below to choose your preferred format:
|
||||||
Creating interactive demos with server-side pre-rendering`),
|
`),
|
||||||
el("li").append(...T`${el("strong", "SSR")} — Server-side rendering with DDE`)
|
el(getLibraryUrl),
|
||||||
|
el("div", { className: "note" }).append(
|
||||||
|
el("p").append(T`
|
||||||
|
Based on your selection, you can use dd<el> in your project like this:
|
||||||
|
`),
|
||||||
|
el(code, { content: `
|
||||||
|
// ESM format (modern JavaScript with import/export)
|
||||||
|
import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.min.js";
|
||||||
|
|
||||||
|
// Or with IIFE format (creates a global DDE object)
|
||||||
|
// <script src="https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/iife-with-signals.min.js"></script>
|
||||||
|
const { el, on } = DDE;
|
||||||
|
`, language: "js" }),
|
||||||
),
|
),
|
||||||
el("p").append(...T`
|
|
||||||
|
el(h3, t`How to Use This Documentation`),
|
||||||
|
el("p").append(T`
|
||||||
|
This guide will take you through dd<el>’s features step by step:
|
||||||
|
`),
|
||||||
|
el("ol", { start: 2 }).append(
|
||||||
|
el("li").append(T`${el("a", { href: "p02-elements.html" }).append(el("strong", "Elements"))} — Creating
|
||||||
|
and manipulating DOM elements`),
|
||||||
|
el("li").append(T`${el("a", { href: "p03-events.html" }).append(el("strong", "Events and Addons"))} —
|
||||||
|
Handling user interactions and lifecycle events`),
|
||||||
|
el("li").append(T`${el("a", { href: "p04-signals.html" }).append(el("strong", "Signals"))} — Adding
|
||||||
|
reactivity to your UI`),
|
||||||
|
el("li").append(T`${el("a", { href: "p05-scopes.html" }).append(el("strong", "Scopes"))} — Managing
|
||||||
|
component lifecycles`),
|
||||||
|
el("li").append(T`${el("a", { href: "p06-customElement.html" }).append(el("strong", "Web Components"))} —
|
||||||
|
Building native custom elements`),
|
||||||
|
el("li").append(T`${el("a", { href: "p07-debugging.html" }).append(el("strong", "Debugging"))} — Tools to
|
||||||
|
help you build and fix your apps`),
|
||||||
|
el("li").append(T`${el("a", { href: "p08-extensions.html" }).append(el("strong", "Extensions"))} —
|
||||||
|
Integrating third-party functionalities`),
|
||||||
|
el("li").append(T`${el("a", { href: "p09-optimization.html" })
|
||||||
|
.append(el("strong", "Performance Optimization"))} — Techniques for optimizing your applications`),
|
||||||
|
el("li").append(T`${el("a", { href: "p10-todomvc.html" }).append(el("strong", "TodoMVC"))} — A real-world
|
||||||
|
application implementation`),
|
||||||
|
el("li").append(T`${el("a", { href: "p11-ssr.html" }).append(el("strong", "SSR"))} — Server-side
|
||||||
|
rendering with dd<el>`),
|
||||||
|
el("li").append(T`${el("a", { href: "p12-ireland.html" }).append(el("strong", "Ireland Components"))} —
|
||||||
|
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.
|
Each section builds on the previous ones, so we recommend following them in order.
|
||||||
Let's get started with the basics of creating elements!
|
Let’s get started with the basics of creating elements!
|
||||||
`),
|
`),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -105,13 +105,8 @@ ${host_nav} a .nav-number {
|
|||||||
}
|
}
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
${host_nav} {
|
${host_nav} {
|
||||||
padding: 0.75rem;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-flow: row wrap;
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5rem;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
border-right: none;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,14 +116,7 @@ ${host_nav} a .nav-number {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
${host_nav} a .nav-number {
|
|
||||||
width: auto;
|
|
||||||
margin-right: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
${host_nav} a:first-child {
|
${host_nav} a:first-child {
|
||||||
margin-bottom: 0;
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
@ -163,7 +151,7 @@ export function header({ info: { href, title, description }, pkg }){
|
|||||||
),
|
),
|
||||||
el("span", {
|
el("span", {
|
||||||
className: "version-badge",
|
className: "version-badge",
|
||||||
"aria-label": "Version",
|
ariaLabel: "Version",
|
||||||
textContent: pkg.version || ""
|
textContent: pkg.version || ""
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
@ -177,13 +165,13 @@ export function header({ info: { href, title, description }, pkg }){
|
|||||||
function nav({ href, pkg }){
|
function nav({ href, pkg }){
|
||||||
return el("nav", {
|
return el("nav", {
|
||||||
role: "navigation",
|
role: "navigation",
|
||||||
"aria-label": "Main navigation",
|
ariaLabel: "Main navigation",
|
||||||
className: nav.name
|
className: nav.name
|
||||||
}).append(
|
}).append(
|
||||||
el("a", {
|
el("a", {
|
||||||
href: pkg.homepage,
|
href: pkg.homepage,
|
||||||
className: "github-link",
|
className: "github-link",
|
||||||
"aria-label": "View on GitHub",
|
ariaLabel: "View on GitHub",
|
||||||
target: "_blank",
|
target: "_blank",
|
||||||
rel: "noopener noreferrer",
|
rel: "noopener noreferrer",
|
||||||
}).append(
|
}).append(
|
||||||
@ -197,11 +185,11 @@ function nav({ href, pkg }){
|
|||||||
return el("a", {
|
return el("a", {
|
||||||
href: isIndex ? "./" : p.href,
|
href: isIndex ? "./" : p.href,
|
||||||
title: p.description || `Go to ${p.title}`,
|
title: p.description || `Go to ${p.title}`,
|
||||||
"aria-current": isCurrent ? "page" : null,
|
ariaCurrent: isCurrent ? "page" : null,
|
||||||
}).append(
|
}).append(
|
||||||
el("span", {
|
el("span", {
|
||||||
className: "nav-number",
|
className: "nav-number",
|
||||||
"aria-hidden": "true",
|
ariaHidden: "true",
|
||||||
textContent: `${i+1}. `
|
textContent: `${i+1}. `
|
||||||
}),
|
}),
|
||||||
p.title
|
p.title
|
||||||
|
@ -3,6 +3,7 @@ import { el, simulateSlots } from "deka-dom-el";
|
|||||||
|
|
||||||
import { header } from "./head.html.js";
|
import { header } from "./head.html.js";
|
||||||
import { prevNext } from "../components/pageUtils.html.js";
|
import { prevNext } from "../components/pageUtils.html.js";
|
||||||
|
import { scrollTop } from "../components/scrollTop.html.js";
|
||||||
|
|
||||||
/** @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 }){
|
||||||
@ -26,6 +27,9 @@ export function simplePage({ pkg, info }){
|
|||||||
|
|
||||||
// Navigation between pages
|
// Navigation between pages
|
||||||
el(prevNext, info)
|
el(prevNext, info)
|
||||||
)
|
),
|
||||||
|
|
||||||
|
// Scroll to top button
|
||||||
|
el(scrollTop),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -47,81 +47,84 @@ const references= {
|
|||||||
};
|
};
|
||||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||||
export function page({ pkg, info }){
|
export function page({ pkg, info }){
|
||||||
const page_id= info.id;
|
|
||||||
return el(simplePage, { info, pkg }).append(
|
return el(simplePage, { info, pkg }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Building user interfaces in JavaScript often involves creating and manipulating DOM elements.
|
Building user interfaces in JavaScript often involves creating and manipulating DOM elements.
|
||||||
DDE provides a simple yet powerful approach to element creation that is declarative, chainable,
|
dd<el> provides a simple yet powerful approach to element creation that is declarative, chainable,
|
||||||
and maintains a clean syntax close to HTML structure.
|
and maintains a clean syntax close to HTML structure.
|
||||||
`),
|
`),
|
||||||
el("div", { class: "callout" }).append(
|
el("div", { className: "callout" }).append(
|
||||||
el("h4", t`DDE Elements: Key Benefits`),
|
el("h4", t`dd<el> Elements: Key Benefits`),
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
el("li", t`Declarative element creation with intuitive property assignment`),
|
el("li", t`Declarative element creation with intuitive property assignment`),
|
||||||
el("li", t`Chainable methods for natural DOM tree construction`),
|
el("li", t`Chainable methods for natural DOM tree construction`),
|
||||||
el("li", t`Simplified component patterns for code reuse`),
|
el("li", t`Simplified component patterns for code reuse`),
|
||||||
el("li", t`Normalized property/attribute handling across browsers`),
|
el("li", t`Normalized declarative property/attribute handling across browsers`),
|
||||||
el("li", t`Smart element return values for cleaner code flow`)
|
el("li", t`Smart element return values for cleaner code flow`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(code, { src: fileURL("./components/examples/elements/intro.js"), page_id }),
|
el(code, { src: fileURL("./components/examples/elements/intro.js") }),
|
||||||
|
|
||||||
el(h3, t`Creating Elements: Native vs DDE`),
|
el(h3, t`Creating Elements: Native vs dd<el>`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
In standard JavaScript, you create DOM elements using the
|
In standard JavaScript, you create DOM elements using the
|
||||||
${el("a", references.mdn_create).append(el("code", "document.createElement()"))} method
|
${el("a", references.mdn_create).append(el("code", "document.createElement()"))} method
|
||||||
and then set properties individually or with Object.assign():
|
and then set properties individually or with ${el("code", "Object.assign()")}:
|
||||||
`),
|
`),
|
||||||
el("div", { class: "illustration" }).append(
|
el("div", { className: "illustration" }).append(
|
||||||
el("div", { class: "comparison" }).append(
|
el("div", { className: "comparison" }).append(
|
||||||
el("div").append(
|
el("div").append(
|
||||||
el("h5", t`Native DOM API`),
|
el("h5", t`Native DOM API`),
|
||||||
el(code, { src: fileURL("./components/examples/elements/native-dom-create.js"), page_id })
|
el(code, { src: fileURL("./components/examples/elements/native-dom-create.js") })
|
||||||
),
|
),
|
||||||
el("div").append(
|
el("div").append(
|
||||||
el("h5", t`DDE Approach`),
|
el("h5", t`dd<el> Approach`),
|
||||||
el(code, { src: fileURL("./components/examples/elements/dde-dom-create.js"), page_id })
|
el(code, { src: fileURL("./components/examples/elements/dde-dom-create.js") })
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
The ${el("code", "el")} function provides a simple wrapper around ${el("code", "document.createElement")}
|
The ${el("code", "el")} function provides a simple wrapper around ${el("code", "document.createElement")}
|
||||||
with enhanced property assignment.
|
with enhanced property assignment.
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/elements/dekaCreateElement.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/elements/dekaCreateElement.js") }),
|
||||||
|
|
||||||
el(h3, t`Advanced Property Assignment`),
|
el(h3, t`Advanced Property Assignment`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
The ${el("code", "assign")} function is the heart of DDE's element property handling. It is internally used
|
The ${el("code", "assign")} function is the heart of dd<el>’s element property handling. It is internally
|
||||||
to assign properties using the ${el("code", "el")} function. ${el("code", "assign")} provides intelligent
|
used to assign properties using the ${el("code", "el")} function. ${el("code", "assign")} provides
|
||||||
assignment of both properties (IDL) and attributes:
|
intelligent assignment of both ${el("a", { textContent: "properties (IDL)", ...references.mdn_idl })}
|
||||||
|
and attributes:
|
||||||
`),
|
`),
|
||||||
el("div", { class: "function-table" }).append(
|
el("div", { className: "function-table" }).append(
|
||||||
el("dl").append(
|
el("dl").append(
|
||||||
el("dt", t`Property vs Attribute Priority`),
|
el("dt", t`Property vs Attribute Priority`),
|
||||||
el("dd", t`Prefers IDL properties, falls back to setAttribute() when no writable property exists`),
|
el("dd", t`Prefers IDL properties, falls back to setAttribute() when no writable property exists`),
|
||||||
|
|
||||||
el("dt", t`Data and ARIA Attributes`),
|
el("dt", t`Data and ARIA Attributes`),
|
||||||
el("dd", t`Both dataset.* and data-* syntaxes supported (same for ARIA)`),
|
el("dd").append(T`Both ${el("code", "dataset.keyName")} and ${el("code", "dataKeyName")} syntaxes are
|
||||||
|
supported (same for ${el("code", "aria")}/${el("code", "ariaset")})`),
|
||||||
|
|
||||||
el("dt", t`Style Handling`),
|
el("dt", t`Style Handling`),
|
||||||
el("dd", t`Accepts string or object notation for style property`),
|
el("dd").append(T`Accepts string or object notation for ${el("code", "style")} property`),
|
||||||
|
|
||||||
el("dt", t`Class Management`),
|
el("dt", t`Class Management`),
|
||||||
el("dd", t`Works with className, class, or classList object for toggling classes`),
|
el("dd").append(T`Works with ${el("code", "className")} (${el("code", "class")}) and ${el("code",
|
||||||
|
"classList")} object for toggling classes`),
|
||||||
|
|
||||||
el("dt", t`Force Modes`),
|
el("dt", t`Force Modes`),
|
||||||
el("dd", t`Use = prefix to force attribute mode, . prefix to force property mode`),
|
el("dd").append(T`Use ${el("code", "=")} prefix to force attribute mode, ${el("code", ".")} prefix to
|
||||||
|
force property mode`),
|
||||||
|
|
||||||
el("dt", t`Attribute Removal`),
|
el("dt", t`Attribute Removal`),
|
||||||
el("dd", t`Pass undefined to remove a property or attribute`)
|
el("dd").append(T`Pass ${el("code", "undefined")} to remove a property or attribute`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/elements/dekaAssign.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/elements/dekaAssign.js") }),
|
||||||
|
|
||||||
el("div", { class: "note" }).append(
|
el("div", { className: "note" }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
You can explore standard HTML element properties in the MDN documentation for
|
You can explore standard HTML element properties in the MDN documentation for
|
||||||
${el("a", { textContent: "HTMLElement", ...references.mdn_el })} (base class)
|
${el("a", { textContent: "HTMLElement", ...references.mdn_el })} (base class)
|
||||||
and specific element interfaces like ${el("a", { textContent: "HTMLParagraphElement", ...references.mdn_p })}.
|
and specific element interfaces like ${el("a", { textContent: "HTMLParagraphElement", ...references.mdn_p })}.
|
||||||
@ -129,75 +132,81 @@ export function page({ pkg, info }){
|
|||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`Building DOM Trees with Chainable Methods`),
|
el(h3, t`Building DOM Trees with Chainable Methods`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
One of the most powerful features of DDE is its approach to building element trees.
|
One of the most powerful features of dd<el> is its approach to building element trees.
|
||||||
Unlike the native DOM API which doesn't return the parent after appendChild(), DDE's
|
Unlike the native DOM API which doesn’t return the parent after ${el("code", "append()")}, dd<el>’s
|
||||||
append() always returns the parent element:
|
${el("code", "append()")} always returns the parent element:
|
||||||
`),
|
`),
|
||||||
el("div", { class: "illustration" }).append(
|
el("div", { className: "illustration" }).append(
|
||||||
el("div", { class: "comparison" }).append(
|
el("div", { className: "comparison" }).append(
|
||||||
el("div", { class: "bad-practice" }).append(
|
el("div", { className: "bad-practice" }).append(
|
||||||
el("h5", t`❌ Native DOM API`),
|
el("h5", t`❌ Native DOM API`),
|
||||||
el(code, { src: fileURL("./components/examples/elements/native-dom-tree.js"), page_id })
|
el(code, { src: fileURL("./components/examples/elements/native-dom-tree.js") })
|
||||||
),
|
),
|
||||||
el("div", { class: "good-practice" }).append(
|
el("div", { className: "good-practice" }).append(
|
||||||
el("h5", t`✅ DDE Approach`),
|
el("h5", t`✅ dd<el> Approach`),
|
||||||
el(code, { src: fileURL("./components/examples/elements/dde-dom-tree.js"), page_id })
|
el(code, { src: fileURL("./components/examples/elements/dde-dom-tree.js") })
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
This chainable pattern is much cleaner and easier to follow, especially for deeply nested elements.
|
This chainable pattern is much cleaner and easier to follow, especially for deeply nested elements.
|
||||||
It also makes it simple to add multiple children to a parent element in a single fluent expression.
|
It also makes it simple to add multiple children to a parent element in a single fluent expression.
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/elements/dekaAppend.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/elements/dekaAppend.js") }),
|
||||||
|
|
||||||
el(h3, t`Using Components to Build UI Fragments`),
|
el(h3, t`Using Components to Build UI Fragments`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
The ${el("code", "el")} function is overloaded to support both tag names and function components.
|
The ${el("code", "el")} function is overloaded to support both tag names and function components.
|
||||||
This lets you refactor complex UI trees into reusable pieces:
|
This lets you refactor complex UI trees into reusable pieces:
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/elements/dekaBasicComponent.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/elements/dekaBasicComponent.js") }),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Component functions receive the properties object as their first argument, just like regular elements.
|
Component functions receive the properties object as their first argument, just like regular elements.
|
||||||
This makes it easy to pass data down to components and create reusable UI fragments.
|
This makes it easy to pass data down to components and create reusable UI fragments.
|
||||||
`),
|
`),
|
||||||
el("div", { class: "tip" }).append(
|
el("div", { className: "tip" }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
It's helpful to use naming conventions similar to native DOM elements for your components.
|
It’s helpful to use naming conventions similar to native DOM elements for your components.
|
||||||
This allows you to use ${el("a", { textContent: "destructuring assignment", ...references.mdn_destruct })}
|
This allows you to keeps your code consistent with the DOM API.
|
||||||
and keeps your code consistent with the DOM API.
|
`),
|
||||||
`)
|
el("p").append(T`
|
||||||
|
Use ${el("a", { textContent: "destructuring assignment", ...references.mdn_destruct })}
|
||||||
|
to extract the properties from the ${el("code", "props")} and pass them to the component element:
|
||||||
|
${el("code", "function component({ className }){ return el(\"p\", { className }); }")} for make
|
||||||
|
templates cleaner.
|
||||||
|
`),
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`Working with SVG and Other Namespaces`),
|
el(h3, t`Working with SVG and Other Namespaces`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
For non-HTML elements like SVG, MathML, or custom namespaces, DDE provides the ${el("code", "elNS")} function
|
For non-HTML elements like SVG, MathML, or custom namespaces, dd<el> provides the ${el("code", "elNS")}
|
||||||
which corresponds to the native ${el("a", references.mdn_ns).append(el("code", "document.createElementNS"))}:
|
function which corresponds to the native ${el("a", references.mdn_ns).append(el("code",
|
||||||
|
"document.createElementNS"))}:
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/elements/dekaElNS.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/elements/dekaElNS.js") }),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
This function returns a namespace-specific element creator, allowing you to work with any element type
|
This function returns a namespace-specific element creator, allowing you to work with any element type
|
||||||
using the same consistent interface.
|
using the same consistent interface.
|
||||||
`),
|
`),
|
||||||
|
|
||||||
el(h3, t`Best Practices for Declarative DOM Creation`),
|
el(h3, t`Best Practices for Declarative DOM Creation`),
|
||||||
el("ol").append(
|
el("ol").append(
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Use component functions for reusable UI fragments:")} Extract common UI patterns
|
${el("strong", "Use component functions for reusable UI fragments:")} Extract common UI patterns
|
||||||
into reusable functions that return elements.
|
into reusable functions that return elements.
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Leverage destructuring for cleaner component code:")} Use
|
${el("strong", "Leverage destructuring for cleaner component code:")} Use
|
||||||
${el("a", { textContent: "destructuring", ...references.mdn_destruct })} to extract properties
|
${el("a", { textContent: "destructuring", ...references.mdn_destruct })} to extract properties
|
||||||
from the props object for cleaner component code.
|
from the props object for cleaner component code.
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Leverage chainable methods for better performance:")} Use chainable methods like
|
${el("strong", "Leverage chainable methods for better performance:")} Use chainable methods
|
||||||
${el("code", ".append()")} to build complex DOM trees for better performance and cleaner code.
|
${el("code", ".append()")} to build complex DOM trees for better performance and cleaner code.
|
||||||
`),
|
`),
|
||||||
),
|
),
|
||||||
|
|
||||||
el(mnemonic)
|
el(mnemonic),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -36,23 +36,17 @@ const references= {
|
|||||||
mdn_mutation: {
|
mdn_mutation: {
|
||||||
href: "https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver",
|
href: "https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver",
|
||||||
},
|
},
|
||||||
/** Readding the element to the DOM fix by Vue */
|
|
||||||
vue_fix: {
|
|
||||||
title: t`Vue and Web Components, lifecycle implementation readding the element to the DOM`,
|
|
||||||
href: "https://vuejs.org/guide/extras/web-components.html#lifecycle",
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||||
export function page({ pkg, info }){
|
export function page({ pkg, info }){
|
||||||
const page_id= info.id;
|
|
||||||
return el(simplePage, { info, pkg }).append(
|
return el(simplePage, { info, pkg }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Events are at the core of interactive web applications. DDE provides a clean, declarative approach to
|
Events are at the core of interactive web applications. dd<el> provides a clean, declarative approach to
|
||||||
handling DOM events and extends this pattern with a powerful Addon system to incorporate additional
|
handling DOM events and extends this pattern with a powerful Addon system to incorporate additional
|
||||||
functionalities into your UI templates.
|
functionalities into your UI templates.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "callout" }).append(
|
el("div", { className: "callout" }).append(
|
||||||
el("h4", t`Why DDE's Event System and Addons Matters`),
|
el("h4", t`Why dd<el>’s Event System and Addons Matters`),
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
el("li", t`Integrate event handling directly in element declarations`),
|
el("li", t`Integrate event handling directly in element declarations`),
|
||||||
el("li", t`Leverage lifecycle events for better component design`),
|
el("li", t`Leverage lifecycle events for better component design`),
|
||||||
@ -62,71 +56,76 @@ export function page({ pkg, info }){
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(code, { src: fileURL("./components/examples/events/intro.js"), page_id }),
|
el(code, { src: fileURL("./components/examples/events/intro.js") }),
|
||||||
|
|
||||||
el(h3, t`Events and Listeners: Two Approaches`),
|
el(h3, t`Events and Listeners: Two Approaches`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
In JavaScript you can listen to native DOM events using
|
In JavaScript you can listen to native DOM events using
|
||||||
${el("a", references.mdn_listen).append(el("code", "element.addEventListener(type, listener, options)"))}.
|
${el("a", references.mdn_listen).append(el("code", "element.addEventListener(type, listener, options)"))}.
|
||||||
DDE provides an alternative approach with arguments ordered differently to better fit its declarative style:
|
dd<el> provides an alternative approach with arguments ordered differently to better fit its declarative
|
||||||
|
style:
|
||||||
`),
|
`),
|
||||||
el("div", { className: "illustration" }).append(
|
el("div", { className: "illustration" }).append(
|
||||||
el("div", { className: "tabs" }).append(
|
el("div", { className: "tabs" }).append(
|
||||||
el("div", { className: "tab" }).append(
|
el("div", { className: "tab" }).append(
|
||||||
el("h5", t`Native DOM API`),
|
el("h5", t`Native DOM API`),
|
||||||
el(code, { content: `element.addEventListener('click', callback, options);`, page_id })
|
el(code, { content: `element.addEventListener("click", callback, options);`, language: "js" })
|
||||||
),
|
),
|
||||||
el("div", { className: "tab" }).append(
|
el("div", { className: "tab" }).append(
|
||||||
el("h5", t`DDE Approach`),
|
el("h5", t`dd<el> Approach`),
|
||||||
el(code, { content: `on('click', callback, options)(element);`, page_id })
|
el(code, { content: `on("click", callback, options)(element);`, language: "js" })
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
The main benefit of DDE's approach is that it works as an Addon, making it easy to integrate
|
The main benefit of dd<el>’s approach is that it works as an Addon (see below), making it easy to integrate
|
||||||
directly into element declarations.
|
directly into element declarations.
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/events/compare.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/events/compare.js") }),
|
||||||
|
|
||||||
el(h3, t`Removing Event Listeners`),
|
el(h3, t`Removing Event Listeners`),
|
||||||
el("div", { className: "note" }).append(
|
el("div", { className: "note" }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Unlike the native addEventListener/removeEventListener pattern, DDE uses the ${el("a", {
|
Unlike the native addEventListener/removeEventListener pattern, dd<el> uses ${el("strong", "only")}
|
||||||
textContent: "AbortSignal", ...references.mdn_abortListener })} for declarative approach for removal:
|
${el("a", { textContent: "AbortSignal", ...references.mdn_abortListener })} for declarative removal:
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/events/abortSignal.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/events/abortSignal.js") }),
|
||||||
|
el("p").append(T`
|
||||||
|
This is the same for signals (see next section) and works well with scopes and library extendability (
|
||||||
|
see scopes and extensions section — mainly ${el("code", "scope.signal")}).
|
||||||
|
`),
|
||||||
|
|
||||||
el(h3, t`Three Ways to Handle Events`),
|
el(h3, t`Three Ways to Handle Events`),
|
||||||
el("div", { className: "tabs" }).append(
|
el("div", { className: "tabs" }).append(
|
||||||
el("div", { className: "tab", "data-tab": "html-attr" }).append(
|
el("div", { className: "tab", dataTab: "html-attr" }).append(
|
||||||
el("h4", t`HTML Attribute Style`),
|
el("h4", t`HTML Attribute Style`),
|
||||||
el(code, { src: fileURL("./components/examples/events/attribute-event.js"), page_id }),
|
el(code, { src: fileURL("./components/examples/events/attribute-event.js") }),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Forces usage as an HTML attribute. Corresponds to
|
Forces usage as an HTML attribute. Corresponds to
|
||||||
${el("code", `<button onclick="console.log(event)">click me</button>`)}. This can be particularly
|
${el("code", `<button onclick="console.log(event)">click me</button>`)}. This can be particularly
|
||||||
useful for SSR scenarios.
|
useful for SSR scenarios.
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
el("div", { className: "tab", "data-tab": "property" }).append(
|
el("div", { className: "tab", dataTab: "property" }).append(
|
||||||
el("h4", t`Property Assignment`),
|
el("h4", t`Property Assignment`),
|
||||||
el(code, { src: fileURL("./components/examples/events/property-event.js"), page_id }),
|
el(code, { src: fileURL("./components/examples/events/property-event.js") }),
|
||||||
el("p", t`Assigns the event handler directly to the element's property.`)
|
el("p", t`Assigns the event handler directly to the element’s property.`)
|
||||||
),
|
),
|
||||||
el("div", { className: "tab", "data-tab": "addon" }).append(
|
el("div", { className: "tab", dataTab: "addon" }).append(
|
||||||
el("h4", t`Addon Approach`),
|
el("h4", t`Addon Approach`),
|
||||||
el(code, { src: fileURL("./components/examples/events/chain-event.js"), page_id }),
|
el(code, { src: fileURL("./components/examples/events/chain-event.js") }),
|
||||||
el("p", t`Uses the addon pattern, see above.`)
|
el("p", t`Uses the addon pattern (so adds the event listener to the element), see above.`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
For a deeper comparison of these approaches, see
|
For a deeper comparison of these approaches, see
|
||||||
${el("a", { textContent: "WebReflection's detailed analysis", ...references.web_events })}.
|
${el("a", { textContent: "WebReflection’s detailed analysis", ...references.web_events })}.
|
||||||
`),
|
`),
|
||||||
|
|
||||||
el(h3, t`Understanding Addons`),
|
el(h3, t`Understanding Addons`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Addons are a powerful pattern in DDE that extends beyond just event handling.
|
Addons are a powerful pattern in dd<el> that extends beyond just event handling.
|
||||||
An Addon is any function that accepts an HTML element as its first parameter.
|
An Addon is any function that accepts an HTML element as its first parameter.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "callout" }).append(
|
el("div", { className: "callout" }).append(
|
||||||
@ -136,26 +135,27 @@ export function page({ pkg, info }){
|
|||||||
el("li", t`Set up lifecycle behaviors`),
|
el("li", t`Set up lifecycle behaviors`),
|
||||||
el("li", t`Integrate third-party libraries`),
|
el("li", t`Integrate third-party libraries`),
|
||||||
el("li", t`Create reusable element behaviors`),
|
el("li", t`Create reusable element behaviors`),
|
||||||
el("li", t`Capture element references`)
|
el("li", t`Capture element references`), // TODO: add example?
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
You can use Addons as ≥3rd argument of the ${el("code", "el")} function, making it possible to
|
You can use Addons as ≥3rd argument of the ${el("code", "el")} function, making it possible to
|
||||||
extend your templates with additional functionality:
|
extend your templates with additional functionality:
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/events/templateWithListeners.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/events/templateWithListeners.js") }),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
As the example shows, you can provide types in JSDoc+TypeScript using the global type
|
As the example shows, you can provide types in JSDoc+TypeScript using the global type
|
||||||
${el("code", "ddeElementAddon")}. Notice how Addons can also be used to get element references.
|
${el("code", "ddeElementAddon")}. Notice how Addons can also be used to get element references.
|
||||||
`),
|
`),
|
||||||
|
|
||||||
el(h3, t`Lifecycle Events`),
|
el(h3, t`Lifecycle Events`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Addons are called immediately when an element is created, even before it's connected to the live DOM.
|
Addons are called immediately when an element is created, even before it’s connected to the live DOM.
|
||||||
You can think of an Addon as an "oncreate" event handler.
|
You can think of an Addon as an “oncreate” event handler.
|
||||||
`),
|
`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
DDE provides three additional lifecycle events that correspond to custom element lifecycle callbacks:
|
dd<el> provides two additional lifecycle events that correspond to ${el("a", { textContent:
|
||||||
|
"custom element", ...references.mdn_customElements })} lifecycle callbacks and component patterns:
|
||||||
`),
|
`),
|
||||||
el("div", { className: "function-table" }).append(
|
el("div", { className: "function-table" }).append(
|
||||||
el("dl").append(
|
el("dl").append(
|
||||||
@ -164,61 +164,74 @@ export function page({ pkg, info }){
|
|||||||
|
|
||||||
el("dt", t`on.disconnected(callback)`),
|
el("dt", t`on.disconnected(callback)`),
|
||||||
el("dd", t`Fires when the element is removed from the DOM`),
|
el("dd", t`Fires when the element is removed from the DOM`),
|
||||||
|
|
||||||
el("dt", t`on.attributeChanged(callback, attributeName)`),
|
|
||||||
el("dd", t`Fires when the specified attribute changes`)
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/events/live-cycle.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/events/live-cycle.js") }),
|
||||||
|
|
||||||
el("div", { className: "note" }).append(
|
el("div", { className: "note" }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
For regular elements (non-custom elements), DDE uses
|
For regular elements (non-custom elements), dd<el> uses ${el("a",
|
||||||
${el("a", references.mdn_mutation).append(el("code", "MutationObserver"), " | MDN")}
|
references.mdn_mutation).append(el("code", "MutationObserver"), " | MDN")} internally to track
|
||||||
internally to track lifecycle events.
|
lifecycle events.
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
|
|
||||||
el("div", { className: "warning" }).append(
|
el("div", { className: "warning" }).append(
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
Always use ${el("code", "on.*")} functions, not ${el("code", "on('dde:*', ...)")}, for proper registration
|
Always use ${el("code", "on.*")} functions as library must ensure proper (MutationObserver)
|
||||||
|
registration, not ${el("code", "on('dde:*', ...)")}, even the native event system is used with event
|
||||||
|
names prefixed with ${el("code", "dde:")}.
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
Use lifecycle events sparingly, as they require internal tracking
|
Use lifecycle events sparingly, as they require internal tracking
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
Leverage parent-child relationships: when a parent is removed, all children are also removed
|
Leverage parent-child relationships: when a parent is removed, all children are also removed
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
…see section later in documentation regarding hosts elements
|
…see section later in documentation regarding hosts elements
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
DDE ensures that connected/disconnected events fire only once for better predictability
|
dd<el> ensures that connected/disconnected events fire only once for better predictability
|
||||||
`)
|
`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`Dispatching Custom Events`),
|
el(h3, t`Utility Helpers`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
This makes it easy to implement component communication through events,
|
You can use the ${el("code", "on.defer")} helper to defer execution to the next event loop.
|
||||||
following standard web platform patterns. The curried approach allows for easy reuse
|
This is useful for example when you wan to set some element properties based on the current element
|
||||||
of event dispatchers throughout your application.
|
body (typically the ${el("code", "<select value=\"...\">")}).
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/events/compareDispatch.js"), page_id }),
|
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("p").append(T`
|
||||||
|
This makes it easy to implement component communication through events, following standard web platform
|
||||||
|
patterns. The curried approach allows for easy reuse of event dispatchers throughout your application.
|
||||||
|
`),
|
||||||
|
el(example, { src: fileURL("./components/examples/events/compareDispatch.js") }),
|
||||||
|
el(code, { src: fileURL("./components/examples/events/dispatch.js") }),
|
||||||
|
|
||||||
el(h3, t`Best Practices`),
|
el(h3, t`Best Practices`),
|
||||||
el("ol").append(
|
el("ol").append(
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Clean up listeners")}: Use AbortSignal to prevent memory leaks
|
${el("strong", "Clean up listeners")}: Use AbortSignal to prevent memory leaks
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Leverage lifecycle events")}: For component setup and teardown
|
${el("strong", "Leverage lifecycle events")}: For component setup and teardown
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Delegate when possible")}: Add listeners to container elements when handling many similar elements
|
${el("strong", "Delegate when possible")}: Add listeners to container elements when handling many
|
||||||
|
similar elements
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Maintain consistency")}: Choose one event binding approach and stick with it
|
${el("strong", "Maintain consistency")}: Choose one event binding approach and stick with it
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
@ -237,6 +250,6 @@ export function page({ pkg, info }){
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(mnemonic)
|
el(mnemonic),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -42,127 +42,128 @@ const references= {
|
|||||||
};
|
};
|
||||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||||
export function page({ pkg, info }){
|
export function page({ pkg, info }){
|
||||||
const page_id= info.id;
|
|
||||||
return el(simplePage, { info, pkg }).append(
|
return el(simplePage, { info, pkg }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Signals provide a simple yet powerful way to create reactive applications with DDE. They handle the
|
Signals provide a simple yet powerful way to create reactive applications with dd<el>. They handle the
|
||||||
fundamental challenge of keeping your UI in sync with changing data in a declarative, efficient way.
|
fundamental challenge of keeping your UI in sync with changing data in a declarative, efficient way.
|
||||||
`),
|
`),
|
||||||
el("div", { class: "callout" }).append(
|
el("div", { className: "callout" }).append(
|
||||||
el("h4", t`What Makes Signals Special?`),
|
el("h4", t`What Makes Signals Special?`),
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
el("li", t`Fine-grained reactivity without complex state management`),
|
el("li", t`Fine-grained reactivity without complex state management`),
|
||||||
el("li", t`Automatic UI updates when data changes`),
|
el("li", t`Automatic UI updates when data changes`),
|
||||||
el("li", t`Clean separation between data, logic, and UI`),
|
el("li", t`Clean separation between data, logic, and UI`),
|
||||||
el("li", t`Small runtime with minimal overhead`),
|
el("li", t`Small runtime with minimal overhead`),
|
||||||
el("li").append(...T`${el("strong", "In future")} no dependencies or framework lock-in`)
|
el("li").append(T`${el("strong", "In future")} no dependencies or framework lock-in`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el(code, { src: fileURL("./components/examples/signals/intro.js"), page_id }),
|
el(code, { src: fileURL("./components/examples/signals/intro.js") }),
|
||||||
|
|
||||||
el(h3, t`The 3-Part Structure of Signals`),
|
el(h3, t`The 3-Part Structure of Signals`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Signals organize your code into three distinct parts, following the
|
Signals organize your code into three distinct parts, following the
|
||||||
${el("a", { textContent: t`3PS principle`, href: "./#h-3ps" })}:
|
${el("a", { textContent: t`3PS principle`, href: "./#h-3ps" })}:
|
||||||
`),
|
`),
|
||||||
el("div", { class: "signal-diagram" }).append(
|
el("div", { className: "signal-diagram" }).append(
|
||||||
el("div", { class: "signal-part" }).append(
|
el("div", { className: "signal-part" }).append(
|
||||||
el("h4", t`PART 1: Create Signal`),
|
el("h4", t`PART 1: Create Signal`),
|
||||||
el(code, { content: "const count = S(0);", page_id }),
|
el(code, { content: "const count = S(0);", language: "js" }),
|
||||||
el("p", t`Define a reactive value that can be observed and changed`)
|
el("p", t`Define a reactive value that can be observed and changed`)
|
||||||
),
|
),
|
||||||
el("div", { class: "signal-part" }).append(
|
el("div", { className: "signal-part" }).append(
|
||||||
el("h4", t`PART 2: React to Changes`),
|
el("h4", t`PART 2: React to Changes`),
|
||||||
el(code, { content: "S.on(count, value => updateUI(value));", page_id }),
|
el(code, { content: "S.on(count, value => updateUI(value));", language: "js" }),
|
||||||
el("p", t`Subscribe to signal changes with callbacks or effects`)
|
el("p", t`Subscribe to signal changes with callbacks or effects`)
|
||||||
),
|
),
|
||||||
el("div", { class: "signal-part" }).append(
|
el("div", { className: "signal-part" }).append(
|
||||||
el("h4", t`PART 3: Update Signal`),
|
el("h4", t`PART 3: Update Signal`),
|
||||||
el(code, { content: "count.set(count.get() + 1);", page_id }),
|
el(code, { content: "count.set(count.get() + 1);", language: "js" }),
|
||||||
el("p", t`Modify the signal value, which automatically triggers updates`)
|
el("p", t`Modify the signal value, which automatically triggers updates`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/signals/signals.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/signals/signals.js") }),
|
||||||
|
|
||||||
el("div", { class: "note" }).append(
|
el("div", { className: "note" }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Signals implement the ${el("a", { textContent: t`Publish–subscribe pattern`, ...references.wiki_pubsub })},
|
Signals implement the ${el("a", { textContent: t`Publish–subscribe pattern`, ...references.wiki_pubsub
|
||||||
a form of ${el("a", { textContent: t`Event-driven programming`, ...references.wiki_event_driven })}.
|
})}, a form of ${el("a", { textContent: t`Event-driven programming`, ...references.wiki_event_driven
|
||||||
This architecture allows different parts of your application to stay synchronized through a shared signal,
|
})}. This architecture allows different parts of your application to stay synchronized through
|
||||||
without direct dependencies on each other. Compare for example with ${el("a", { textContent:
|
a shared signal, without direct dependencies on each other. Compare for example with ${el("a", {
|
||||||
t`fpubsub library`, ...references.fpubsub })}.
|
textContent: t`fpubsub library`, ...references.fpubsub })}.
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`Signal Essentials: Core API`),
|
el(h3, t`Signal Essentials: Core API`),
|
||||||
el("div", { class: "function-table" }).append(
|
el("div", { className: "function-table" }).append(
|
||||||
el("dl").append(
|
el("dl").append(
|
||||||
el("dt", t`Creating a Signal`),
|
el("dt", t`Creating a Signal`),
|
||||||
el("dd", t`S(initialValue) → creates a signal with the given value`),
|
el("dd", t`S(initialValue) → creates a signal with the given value`),
|
||||||
|
|
||||||
el("dt", t`Reading a Signal`),
|
el("dt", t`Reading a Signal`),
|
||||||
el("dd", t`signal.get() → returns the current value`),
|
el("dd", t`signal.get() → returns the current value`),
|
||||||
|
|
||||||
el("dt", t`Writing to a Signal`),
|
el("dt", t`Writing to a Signal`),
|
||||||
el("dd", t`signal.set(newValue) → updates the value and notifies subscribers`),
|
el("dd", t`signal.set(newValue) → updates the value and notifies subscribers`),
|
||||||
|
|
||||||
el("dt", t`Subscribing to Changes`),
|
el("dt", t`Subscribing to Changes`),
|
||||||
el("dd", t`S.on(signal, callback) → runs callback whenever signal changes`),
|
el("dd", t`S.on(signal, callback) → runs callback whenever signal changes`),
|
||||||
|
|
||||||
el("dt", t`Unsubscribing`),
|
el("dt", t`Unsubscribing`),
|
||||||
el("dd").append(...T`S.on(signal, callback, { signal: abortController.signal }) → Similarly to the
|
el("dd").append(T`S.on(signal, callback, { signal: abortController.signal }) → Similarly to the
|
||||||
${el("code", "on")} function to register DOM events listener.`)
|
${el("code", "on")} function to register DOM events listener.`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Signals can be created with any type of value, but they work best with
|
Signals can be created with any type of value, but they work best with ${el("a", { textContent:
|
||||||
${el("a", { textContent: t`primitive types`, ...references.mdn_primitive })} like strings, numbers, and booleans.
|
t`primitive types`, ...references.mdn_primitive })} like strings, numbers, and booleans. For complex
|
||||||
For complex data types like objects and arrays, you'll want to use Actions (covered below).
|
data types like objects and arrays, you’ll want to use Actions (covered below).
|
||||||
`),
|
`),
|
||||||
|
|
||||||
el(h3, t`Derived Signals: Computed Values`),
|
el(h3, t`Derived Signals: Computed Values`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Computed values (also called derived signals) automatically update when their dependencies change.
|
Computed values (also called derived signals) automatically update when their dependencies change.
|
||||||
Create them by passing a function to S():
|
Create them by passing ${el("strong", "a function")} to ${el("code", "S()")}:
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/signals/derived.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/signals/derived.js") }),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Derived signals are read-only - you can't call .set() on them. Their value is always computed
|
Derived signals are read-only - you can’t call ${el("code", ".set()")} on them. Their value is always
|
||||||
from their dependencies. They're perfect for transforming or combining data from other signals.
|
computed from their dependencies. They’re perfect for transforming or combining data from other signals.
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/signals/computations-abort.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/signals/computations-abort.js") }),
|
||||||
|
|
||||||
el(h3, t`Signal Actions: For Complex State`),
|
el(h3, t`Signal Actions: For Complex State`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
When working with objects, arrays, or other complex data structures, Signal Actions provide
|
When working with objects, arrays, or other complex data structures. Signal Actions provide
|
||||||
a structured way to modify state while maintaining reactivity.
|
a structured way to modify state while maintaining reactivity.
|
||||||
`),
|
`),
|
||||||
el("div", { class: "illustration" }).append(
|
el("div", { className: "illustration" }).append(
|
||||||
el("h4", t`Actions vs. Direct Mutation`),
|
el("h4", t`Actions vs. Direct Mutation`),
|
||||||
el("div", { class: "comparison" }).append(
|
el("div", { className: "comparison" }).append(
|
||||||
el("div", { class: "good-practice" }).append(
|
el("div", { className: "good-practice" }).append(
|
||||||
el("h5", t`✅ With Actions`),
|
el("h5", t`✅ With Actions`),
|
||||||
el(code, { content: `const todos = S([], {
|
el(code, { content: `
|
||||||
|
const todos = S([], {
|
||||||
add(text) {
|
add(text) {
|
||||||
this.value.push(text);
|
this.value.push(text);
|
||||||
// Subscribers notified automatically
|
// Subscribers notified automatically
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Use the action
|
// Use the action
|
||||||
S.action(todos, "add", "New todo");`, page_id })
|
S.action(todos, "add", "New todo");
|
||||||
|
`, language: "js" })
|
||||||
),
|
),
|
||||||
el("div", { class: "bad-practice" }).append(
|
el("div", { className: "bad-practice" }).append(
|
||||||
el("h5", t`❌ Without Actions`),
|
el("h5", t`❌ Without Actions`),
|
||||||
el(code, { content: `
|
el(code, { content: `
|
||||||
const todos = S([]);
|
const todos = S([]);
|
||||||
// Directly mutating the array
|
// Directly mutating the array
|
||||||
const items = todos.get();
|
const items = todos.get();
|
||||||
items.push("New todo");
|
items.push("New todo");
|
||||||
// This WON'T trigger updates!`, page_id }))
|
// This WON’T trigger updates!
|
||||||
|
`, language: "js" }))
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
In some way, you can compare it with ${el("a", { textContent: "useReducer", ...references.mdn_use_reducer })}
|
In some way, you can compare it with ${el("a", { textContent: "useReducer", ...references.mdn_use_reducer })}
|
||||||
hook from React. So, the ${el("code", "S(<data>, <actions>)")} pattern creates a store “machine”. We can
|
hook from React. So, the ${el("code", "S(<data>, <actions>)")} pattern creates a store “machine”. We can
|
||||||
then invoke (dispatch) registered action by calling ${el("code", "S.action(<signal>, <name>, ...<args>)")}
|
then invoke (dispatch) registered action by calling ${el("code", "S.action(<signal>, <name>, ...<args>)")}
|
||||||
@ -170,9 +171,9 @@ items.push("New todo");
|
|||||||
${el("code", "this.stopPropagation()")} in the method representing the given action. As it can be seen in
|
${el("code", "this.stopPropagation()")} in the method representing the given action. As it can be seen in
|
||||||
examples, the “store” value is available also in the function for given action (${el("code", "this.value")}).
|
examples, the “store” value is available also in the function for given action (${el("code", "this.value")}).
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/signals/actions-demo.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/signals/actions-demo.js") }),
|
||||||
|
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Actions provide these benefits:
|
Actions provide these benefits:
|
||||||
`),
|
`),
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
@ -181,33 +182,37 @@ items.push("New todo");
|
|||||||
el("li", t`Prevent accidental direct mutations`),
|
el("li", t`Prevent accidental direct mutations`),
|
||||||
el("li", t`Act similar to reducers in other state management libraries`)
|
el("li", t`Act similar to reducers in other state management libraries`)
|
||||||
),
|
),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Here's a more complete example of a todo list using signal actions:
|
Here’s a more complete example of a todo list using signal actions:
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/signals/actions-todos.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/signals/actions-todos.js") }),
|
||||||
|
|
||||||
el("div", { class: "tip" }).append(
|
el("div", { className: "tip" }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
${el("strong", "Special Action Methods")}: Signal actions can implement special lifecycle hooks:
|
${el("strong", "Special Action Methods")}: Signal actions can implement special lifecycle hooks:
|
||||||
`),
|
`),
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
el("li", t`[S.symbols.onclear]() - Called when the signal is cleared. Use it to clean up resources.`),
|
el("li").append(T`
|
||||||
|
${el("code", "[S.symbols.onclear]()")} - Called when the signal is cleared. Use it to clean up
|
||||||
|
resources.
|
||||||
|
`),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`Connecting Signals to the DOM`),
|
el(h3, t`Connecting Signals to the DOM`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Signals really shine when connected to your UI. DDE provides several ways to bind signals to DOM elements:
|
Signals really shine when connected to your UI. dd<el> provides several ways to bind signals to DOM elements:
|
||||||
`),
|
`),
|
||||||
|
|
||||||
el("div", { class: "tabs" }).append(
|
el("div", { className: "tabs" }).append(
|
||||||
el("div", { class: "tab", "data-tab": "attributes" }).append(
|
el("div", { className: "tab", dataTab: "attributes" }).append(
|
||||||
el("h4", t`Reactive Attributes`),
|
el("h4", t`Reactive Attributes`),
|
||||||
el("p", t`Bind signal values directly to element attributes, properties, or styles:`),
|
el("p", t`Bind signal values directly to element attributes, properties, or styles:`),
|
||||||
el(code, { content: `// Create a signal
|
el(code, { content: `
|
||||||
|
// Create a signal
|
||||||
const color = S("blue");
|
const color = S("blue");
|
||||||
|
|
||||||
// Bind it to an element's style
|
// Bind it to an element’s style
|
||||||
el("div", {
|
el("div", {
|
||||||
style: {
|
style: {
|
||||||
color, // Updates when signal changes
|
color, // Updates when signal changes
|
||||||
@ -216,15 +221,17 @@ el("div", {
|
|||||||
}, "This text changes color");
|
}, "This text changes color");
|
||||||
|
|
||||||
// Later:
|
// Later:
|
||||||
color.set("red"); // UI updates automatically`, page_id })
|
color.set("red"); // UI updates automatically
|
||||||
|
`, language: "js" }),
|
||||||
),
|
),
|
||||||
el("div", { class: "tab", "data-tab": "elements" }).append(
|
el("div", { className: "tab", dataTab: "elements" }).append(
|
||||||
el("h4", t`Reactive Elements`),
|
el("h4", t`Reactive Elements`),
|
||||||
el("p", t`Dynamically create or update elements based on signal values:`),
|
el("p", t`Dynamically create or update elements based on signal values:`),
|
||||||
el(code, { content: `// Create an array signal
|
el(code, { content: `
|
||||||
|
// Create an array signal
|
||||||
const items = S(["Apple", "Banana", "Cherry"]);
|
const items = S(["Apple", "Banana", "Cherry"]);
|
||||||
|
|
||||||
// Create a dynamic list that updates when items change
|
// Create a dynamic list that updates when items change
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
S.el(items, items =>
|
S.el(items, items =>
|
||||||
items.map(item => el("li", item))
|
items.map(item => el("li", item))
|
||||||
@ -232,61 +239,129 @@ el("ul").append(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Later:
|
// Later:
|
||||||
S.action(items, "push", "Dragonfruit"); // List updates automatically`, page_id })
|
S.action(items, "push", "Dragonfruit"); // List updates automatically
|
||||||
|
`, language: "js" }),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
The ${el("code", "assign")} and ${el("code", "el")} functions detect signals automatically and handle binding.
|
The ${el("code", "assign")} and ${el("code", "el")} functions detect signals automatically and handle binding.
|
||||||
You can use special properties like ${el("code", "dataset")}, ${el("code", "ariaset")}, and
|
You can use special properties like ${el("code", "dataset")}, ${el("code", "ariaset")}, and
|
||||||
${el("code", "classList")} for fine-grained control over specific attribute types.
|
${el("code", "classList")} for fine-grained control over specific attribute types.
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/signals/dom-attrs.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/signals/dom-attrs.js") }),
|
||||||
|
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
${el("code", "S.el()")} is especially powerful for conditional rendering and lists:
|
${el("code", "S.el()")} is especially powerful for conditional rendering and lists:
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/signals/dom-el.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/signals/dom-el.js") }),
|
||||||
|
|
||||||
el(h3, t`Best Practices for Signals`),
|
el(h3, t`Best Practices for Signals`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Follow these guidelines to get the most out of signals:
|
Follow these guidelines to get the most out of signals:
|
||||||
`),
|
`),
|
||||||
el("ol").append(
|
el("ol").append(
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Keep signals small and focused")}: Use many small signals rather than a few large ones
|
${el("strong", "Keep signals small and focused")}: Use many small signals rather than a few large ones
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Use derived signals for computations")}: Don't recompute values in multiple places
|
${el("strong", "Use derived signals for computations")}: Don’t recompute values in multiple places
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Clean up signal subscriptions")}: Use AbortController or scope.host() to prevent memory leaks
|
${el("strong", "Clean up signal subscriptions")}: Use AbortController (scope.host()) to prevent memory
|
||||||
|
leaks
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Use actions for complex state")}: Don't directly mutate objects or arrays in signals
|
${el("strong", "Use actions for complex state")}: Don’t directly mutate objects or arrays in signals
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Avoid infinite loops")}: Be careful when one signal updates another in a subscription
|
${el("strong", "Avoid infinite loops")}: Be careful when one signal updates another in a subscription
|
||||||
`)
|
`),
|
||||||
|
),
|
||||||
|
el("p").append(T`
|
||||||
|
While signals provide powerful reactivity for complex UI interactions, they’re not always necessary.
|
||||||
|
A good approach is to started with variables/constants and when necessary, convert them to signals.
|
||||||
|
`),
|
||||||
|
|
||||||
|
el("div", { className: "tabs" }).append(
|
||||||
|
el("div", { className: "tab", dataTab: "events" }).append(
|
||||||
|
el("h4", t`We can process form events without signals`),
|
||||||
|
el("p", t`This can be used when the form data doesn’t need to be reactive and we just waiting for
|
||||||
|
results.`),
|
||||||
|
el(code, { content: `
|
||||||
|
const onFormSubmit = on("submit", e => {
|
||||||
|
e.preventDefault();
|
||||||
|
const formData = new FormData(e.currentTarget);
|
||||||
|
// this can be sent to a server
|
||||||
|
// or processed locally
|
||||||
|
// e.g.: console.log(Object.fromEntries(formData))
|
||||||
|
});
|
||||||
|
// …
|
||||||
|
return el("form", null, onFormSubmit).append(
|
||||||
|
// …
|
||||||
|
);
|
||||||
|
`, language: "js" })
|
||||||
),
|
),
|
||||||
|
|
||||||
el("div", { class: "troubleshooting" }).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, { content: `
|
||||||
|
let canSubmit = false;
|
||||||
|
|
||||||
|
const onFormSubmit = on("submit", e => {
|
||||||
|
e.preventDefault();
|
||||||
|
if(!canSubmit) return; // some message
|
||||||
|
// …
|
||||||
|
});
|
||||||
|
const onAllowSubmit = on("click", e => {
|
||||||
|
canSubmit = true;
|
||||||
|
});
|
||||||
|
`, language: "js" }),
|
||||||
|
),
|
||||||
|
|
||||||
|
el("div", { className: "tab", dataTab: "state" }).append(
|
||||||
|
el("h4", t`Using signals`),
|
||||||
|
el("p", t`We use this when we need to reflect changes for example in the UI (e.g. enable/disable
|
||||||
|
buttons).`),
|
||||||
|
el(code, { content: `
|
||||||
|
const canSubmit = S(false);
|
||||||
|
|
||||||
|
const onFormSubmit = on("submit", e => {
|
||||||
|
e.preventDefault();
|
||||||
|
// …
|
||||||
|
});
|
||||||
|
const onAllowSubmit = on("click", e => {
|
||||||
|
canSubmit.set(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
return el("form", null, onFormSubmit).append(
|
||||||
|
// ...
|
||||||
|
el("button", { textContent: "Allow Submit", type: "button" }, onAllowSubmit),
|
||||||
|
el("button", { disabled: S(()=> !canSubmit), textContent: "Submit" })
|
||||||
|
);
|
||||||
|
`, language: "js" }),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
el("div", { className: "troubleshooting" }).append(
|
||||||
el("h4", t`Common Signal Pitfalls`),
|
el("h4", t`Common Signal Pitfalls`),
|
||||||
el("dl").append(
|
el("dl").append(
|
||||||
el("dt", t`UI not updating when array/object changes`),
|
el("dt", t`UI not updating when array/object changes`),
|
||||||
el("dd", t`Use signal actions instead of direct mutation`),
|
el("dd", t`Use signal actions instead of direct mutation`),
|
||||||
|
|
||||||
|
el("dt", t`UI not updating`),
|
||||||
|
el("dd").append(T`Ensure you passing the (correct) signal not its value (${el("code", "signal")} vs
|
||||||
|
${el("code", "signal.get()")})`),
|
||||||
|
|
||||||
el("dt", t`Infinite update loops`),
|
el("dt", t`Infinite update loops`),
|
||||||
el("dd", t`Check for circular dependencies between signals`),
|
el("dd", t`Check for circular dependencies between signals`),
|
||||||
|
|
||||||
el("dt", t`Memory leaks`),
|
|
||||||
el("dd", t`Use AbortController or scope.host() to clean up subscriptions`),
|
|
||||||
|
|
||||||
el("dt", t`Multiple elements updating unnecessarily`),
|
el("dt", t`Multiple elements updating unnecessarily`),
|
||||||
el("dd", t`Split large signals into smaller, more focused ones`)
|
el("dd", t`Split large signals into smaller, more focused ones`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(mnemonic)
|
el(mnemonic),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import { simplePage } from "./layout/simplePage.html.js";
|
|||||||
import { example } from "./components/example.html.js";
|
import { example } from "./components/example.html.js";
|
||||||
import { h3 } from "./components/pageUtils.html.js";
|
import { h3 } from "./components/pageUtils.html.js";
|
||||||
import { mnemonic } from "./components/mnemonic/scopes-init.js";
|
import { mnemonic } from "./components/mnemonic/scopes-init.js";
|
||||||
import { code } from "./components/code.html.js";
|
import { code, pre } from "./components/code.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= {
|
||||||
@ -27,157 +27,159 @@ const references= {
|
|||||||
};
|
};
|
||||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||||
export function page({ pkg, info }){
|
export function page({ pkg, info }){
|
||||||
const page_id= info.id;
|
|
||||||
return el(simplePage, { info, pkg }).append(
|
return el(simplePage, { info, pkg }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
For state-less components we can use functions as UI components (see “Elements” page). But in real life,
|
For state-less components we can use functions as UI components (see “Elements” page). But in real life,
|
||||||
we may need to handle the component's life-cycle and provide JavaScript the way to properly use
|
we may need to handle the component’s life-cycle and provide JavaScript the way to properly use
|
||||||
the ${el("a", { textContent: t`Garbage collection`, ...references.garbage_collection })}.
|
the ${el("a", { textContent: t`Garbage collection`, ...references.garbage_collection })}.
|
||||||
`),
|
`),
|
||||||
el(code, { src: fileURL("./components/examples/scopes/intro.js"), page_id }),
|
el(code, { src: fileURL("./components/examples/scopes/intro.js") }),
|
||||||
el("p").append(...T`The library therefore uses ${el("em", t`scopes`)} to provide these functionalities.`),
|
el("p").append(T`The library therefore uses ${el("em", t`scopes`)} to provide these functionalities.`),
|
||||||
|
|
||||||
el(h3, t`Understanding Host Elements and Scopes`),
|
el(h3, t`Understanding Host Elements and Scopes`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
The ${el("strong", "host")} is the name for the element representing the component. This is typically the
|
The ${el("strong", "host")} is the name for the element representing the component. This is typically the
|
||||||
element returned by a function. To get a reference, you can use ${el("code", "scope.host()")}. To apply addons,
|
element returned by a function. To get a reference, you can use ${el("code", "scope.host()")}. To apply addons,
|
||||||
just use ${el("code", "scope.host(...<addons>)")}.
|
just use ${el("code", "scope.host(...<addons>)")}.
|
||||||
`),
|
`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Scopes are primarily needed when signals are used in DOM templates (with ${el("code", "el")}, ${el("code",
|
Scopes are primarily needed when signals are used in DOM templates (with ${el("code", "el")}, ${el("code",
|
||||||
"assign")}, or ${el("code", "S.el")}). They provide a way for automatically removing signal listeners
|
"assign")}, or ${el("code", "S.el")}). They provide a way for automatically removing signal listeners
|
||||||
and cleaning up unused signals when components are removed from the DOM.
|
and cleaning up unused signals when components are removed from the DOM.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "illustration" }).append(
|
el("div", { className: "illustration" }).append(
|
||||||
el("h4", t`Component Anatomy`),
|
el("h4", t`Component Anatomy`),
|
||||||
el("pre").append(el("code", `
|
el(pre, { content: `
|
||||||
// 1. Component scope created
|
// 1. Component scope created
|
||||||
el(MyComponent);
|
el(MyComponent);
|
||||||
|
|
||||||
function MyComponent() {
|
function MyComponent() {
|
||||||
// 2. access the host element
|
// 2. access the host element (or other scope related values)
|
||||||
const { host } = scope;
|
const { host } = scope;
|
||||||
|
|
||||||
// 3. Add behavior to host
|
// 3. Add behavior to host
|
||||||
host(
|
host(
|
||||||
on.click(handleClick)
|
on.click(handleClick)
|
||||||
);
|
);
|
||||||
|
|
||||||
// 4. Return the host element
|
// 4. Return the host element
|
||||||
return el("div", {
|
return el("div", {
|
||||||
className: "my-component"
|
className: "my-component"
|
||||||
}).append(
|
}).append(
|
||||||
el("h2", "Title"),
|
el("h2", "Title"),
|
||||||
el("p", "Content")
|
el("p", "Content"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
`.trim()))
|
` })
|
||||||
),
|
),
|
||||||
el("div", { className: "function-table" }).append(
|
el("div", { className: "function-table" }).append(
|
||||||
el("h4", t`scope.host()`),
|
el("h4", t`scope.host()`),
|
||||||
el("dl").append(
|
el("dl").append(
|
||||||
el("dt", t`When called with no arguments`),
|
el("dt", t`When called with no arguments`),
|
||||||
el("dd", t`Returns a reference to the host element (the root element of your component)`),
|
el("dd", t`Returns a reference to the host element (the root element of your component)`),
|
||||||
|
|
||||||
el("dt", t`When called with addons/callbacks`),
|
el("dt", t`When called with addons/callbacks`),
|
||||||
el("dd", t`Applies the addons to the host element and returns the host element`)
|
el("dd", t`Applies the addons to the host element (and returns the host element)`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/scopes/scopes-and-hosts.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/scopes/scopes-and-hosts.js") }),
|
||||||
|
|
||||||
el("div", { className: "tip" }).append(
|
el("div", { className: "tip" }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
${el("strong", "Best Practice:")} Always capture the host reference at the beginning of your component
|
${el("strong", "Best Practice:")} Always capture the host reference (or other scope related values) at
|
||||||
function using ${el("code", "const { host } = scope")} to avoid scope-related issues, especially with
|
the beginning of your component function using ${el("code", "const { host } = scope")} to avoid
|
||||||
asynchronous code.
|
scope-related issues, especially with ${el("em", "asynchronous code")}.
|
||||||
`),
|
`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
If you are interested in the implementation details, see Class-Based Components section.
|
If you are interested in the implementation details, see Class-Based Components section.
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
el(code, { src: fileURL("./components/examples/scopes/good-practise.js"), page_id }),
|
el(code, { src: fileURL("./components/examples/scopes/good-practise.js") }),
|
||||||
|
|
||||||
el(h3, t`Class-Based Components`),
|
el(h3, t`Class-Based Components`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
While functional components are the primary pattern in DDE, you can also create class-based components.
|
While functional components are the primary pattern in dd<el>, you can also create class-based components.
|
||||||
For this, we implement function ${el("code", "elClass")} and use it to demonstrate implementation details
|
For this, we implement function ${el("code", "elClass")} and use it to demonstrate implementation details
|
||||||
for better understanding of the scope logic.
|
for better understanding of the scope logic.
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/scopes/class-component.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/scopes/class-component.js") }),
|
||||||
|
|
||||||
el(h3, t`Automatic Cleanup with Scopes`),
|
el(h3, t`Automatic Cleanup with Scopes`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
One of the most powerful features of scopes is automatic cleanup when components are removed from the DOM.
|
One of the most powerful features of scopes is automatic cleanup when components are removed from the DOM.
|
||||||
This prevents memory leaks and ensures resources are properly released.
|
This prevents memory leaks and ensures resources are properly released.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "illustration" }).append(
|
el("div", { className: "illustration" }).append(
|
||||||
el("h4", t`Lifecycle Flow`),
|
el("h4", t`Lifecycle Flow`),
|
||||||
el("pre").append(el("code", `
|
el(pre, { content: `
|
||||||
1. Component created → scope established
|
1. Component created → scope established
|
||||||
2. Component added to DOM → connected event
|
2. Component added to DOM → connected event
|
||||||
3. Component interactions happen
|
3. Component interactions happen
|
||||||
4. Component removed from DOM → disconnected event
|
4. Component removed from DOM → disconnected event
|
||||||
5. Automatic cleanup of:
|
5. Automatic cleanup of:
|
||||||
- Event listeners
|
- Event listeners (browser)
|
||||||
- Signal subscriptions
|
- Signal subscriptions (dd<el> and browser)
|
||||||
- Custom cleanup code
|
- Custom cleanup code (dd<el> and user)
|
||||||
`))
|
` })
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/scopes/cleaning.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/scopes/cleaning.js") }),
|
||||||
|
|
||||||
el("div", { className: "note" }).append(
|
el("div", { className: "note" }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
In this example, when you click "Remove", the component is removed from the DOM, and all its associated
|
In this example, when you click "Remove", the component is removed from the DOM, and all its associated
|
||||||
resources are automatically cleaned up, including the signal subscription that updates the text content.
|
resources are automatically cleaned up, including ${el("em",
|
||||||
This happens because the library internally registers a disconnected event handler on the host element.
|
"the signal subscription that updates the text content")}. This happens because the library
|
||||||
|
internally registers a disconnected event handler on the host element.
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`Declarative vs Imperative Components`),
|
el(h3, t`Declarative vs Imperative Components`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
The library DOM API and signals work best when used declaratively. It means you split your app's logic
|
The library DOM API and signals work best when used declaratively. It means you split your app’s logic
|
||||||
into three parts as introduced in ${el("a", { textContent: "Signals", ...references.signals })}.
|
into three parts as introduced in ${el("a", { textContent: "Signals (3PS)", ...references.signals })}.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "note" }).append(
|
el("div", { className: "note" }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Strictly speaking, the imperative way of using the library is not prohibited. Just be careful to avoid
|
Strictly speaking, the imperative way of using the library is not prohibited. Just be careful to avoid
|
||||||
mixing the declarative approach (using signals) with imperative manipulation of elements.
|
mixing the declarative approach (using signals) with imperative manipulation of elements.
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
el("div", { className: "tabs" }).append(
|
el("div", { className: "tabs" }).append(
|
||||||
el("div", { className: "tab", "data-tab": "declarative" }).append(
|
el("div", { className: "tab", dataTab: "declarative" }).append(
|
||||||
el("h4", t`✅ Declarative Approach`),
|
el("h4", t`✅ Declarative Approach`),
|
||||||
el("p", t`Define what your UI should look like based on state:`),
|
el("p", t`Define what your UI should look like based on state:`),
|
||||||
el(code, { src: fileURL("./components/examples/scopes/declarative.js"), page_id })
|
el(code, { src: fileURL("./components/examples/scopes/declarative.js") })
|
||||||
),
|
),
|
||||||
el("div", { className: "tab", "data-tab": "imperative" }).append(
|
el("div", { className: "tab", dataTab: "imperative" }).append(
|
||||||
el("h4", t`⚠️ Imperative Approach`),
|
el("h4", t`⚠️ Imperative Approach`),
|
||||||
el("p", t`Manually update the DOM in response to events:`),
|
el("p", t`Manually update the DOM in response to events:`),
|
||||||
el(code, { src: fileURL("./components/examples/scopes/imperative.js"), page_id })
|
el(code, { src: fileURL("./components/examples/scopes/imperative.js") })
|
||||||
),
|
),
|
||||||
el("div", { className: "tab", "data-tab": "mixed" }).append(
|
el("div", { className: "tab", dataTab: "mixed" }).append(
|
||||||
el("h4", t`❌ Mixed Approach`),
|
el("h4", t`❌ Mixed Approach`),
|
||||||
el("p", t`This approach should be avoided:`),
|
el("p", t`This approach should be avoided:`),
|
||||||
el(code, { src: fileURL("./components/examples/scopes/mixed.js"), page_id })
|
el(code, { src: fileURL("./components/examples/scopes/mixed.js") })
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`Best Practices for Scopes and Components`),
|
el(h3, t`Best Practices for Scopes and Components`),
|
||||||
el("ol").append(
|
el("ol").append(
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Capture host early:")} Use ${el("code", "const { host } = scope")} at component start
|
${el("strong", "Capture host early:")} Use ${el("code", "const { host } = scope")} at component start
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Define signals as constants:")} ${el("code", "const counter = S(0);")}
|
${el("strong", "Define signals as constants:")} ${el("code", "const counter = S(0);")}
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Prefer declarative patterns:")} Use signals to drive UI updates rather than manual DOM manipulation
|
${el("strong", "Prefer declarative patterns:")} Use signals to drive UI updates rather than manual DOM
|
||||||
|
manipulation
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Keep components focused:")} Each component should do one thing well
|
${el("strong", "Keep components focused:")} Each component should do one thing well
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Add explicit cleanup:")} For resources not managed by DDE, use ${el("code", "on.disconnected")}
|
${el("strong", "Add explicit cleanup:")} For resources not managed by dd<el>, use ${el("code",
|
||||||
|
"on.disconnected")}
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
|
|
||||||
@ -194,7 +196,7 @@ function MyComponent() {
|
|||||||
el("dd", t`Use arrow functions or .bind() to preserve context`),
|
el("dd", t`Use arrow functions or .bind() to preserve context`),
|
||||||
|
|
||||||
el("dt", t`Mixing declarative and imperative styles`),
|
el("dt", t`Mixing declarative and imperative styles`),
|
||||||
el("dd", t`Choose one approach and be consistent throughout a component`)
|
el("dd", t`Choose one approach and be consistent throughout a component(s)`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { T, t } from "./utils/index.js";
|
import { T, t } from "./utils/index.js";
|
||||||
export const info= {
|
export const info= {
|
||||||
title: t`Web Components`,
|
title: t`Web Components`,
|
||||||
fullTitle: t`Using Web Components with DDE: Better Together`,
|
fullTitle: t`Using Web Components with dd<el>: Better Together`,
|
||||||
description: t`Using custom elements in combination with DDE`,
|
description: t`Using custom elements in combination with dd<el>`,
|
||||||
};
|
};
|
||||||
|
|
||||||
import { el } from "deka-dom-el";
|
import { el } from "deka-dom-el";
|
||||||
@ -10,7 +10,7 @@ import { simplePage } from "./layout/simplePage.html.js";
|
|||||||
import { example } from "./components/example.html.js";
|
import { example } from "./components/example.html.js";
|
||||||
import { h3 } from "./components/pageUtils.html.js";
|
import { h3 } from "./components/pageUtils.html.js";
|
||||||
import { mnemonic } from "./components/mnemonic/customElement-init.js";
|
import { mnemonic } from "./components/mnemonic/customElement-init.js";
|
||||||
import { code } from "./components/code.html.js";
|
import { code, pre } from "./components/code.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= {
|
||||||
@ -49,51 +49,55 @@ const references= {
|
|||||||
title: t`Everything you need to know about Shadow DOM (github repo praveenpuglia/shadow-dom-in-depth)`,
|
title: t`Everything you need to know about Shadow DOM (github repo praveenpuglia/shadow-dom-in-depth)`,
|
||||||
href: "https://github.com/praveenpuglia/shadow-dom-in-depth",
|
href: "https://github.com/praveenpuglia/shadow-dom-in-depth",
|
||||||
},
|
},
|
||||||
|
/** Decorators */
|
||||||
|
decorators: {
|
||||||
|
title: t`JavaScript Decorators: An In-depth Guide`,
|
||||||
|
href: "https://www.sitepoint.com/javascript-decorators-what-they-are/",
|
||||||
|
}
|
||||||
};
|
};
|
||||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||||
export function page({ pkg, info }){
|
export function page({ pkg, info }){
|
||||||
const page_id= info.id;
|
|
||||||
return el(simplePage, { info, pkg }).append(
|
return el(simplePage, { info, pkg }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
DDE pairs powerfully with ${el("a", references.mdn_web_components).append(el("strong", t`Web Components`))}
|
dd<el> pairs powerfully with ${el("a", references.mdn_web_components).append(el("strong", t`Web
|
||||||
to create reusable, encapsulated custom elements with all the benefits of DDE's declarative DOM
|
Components`))} to create reusable, encapsulated custom elements with all the benefits of dd<el>’s
|
||||||
construction and reactivity system.
|
declarative DOM construction and reactivity system.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "callout" }).append(
|
el("div", { className: "callout" }).append(
|
||||||
el("h4", t`Why Combine DDE with Web Components?`),
|
el("h4", t`Why Combine dd<el> with Web Components?`),
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
el("li", t`Declarative DOM creation within your components`),
|
el("li", t`Declarative DOM creation within your components`),
|
||||||
el("li", t`Reactive attribute updates through signals`),
|
el("li", t`Reactive attribute updates through signals`),
|
||||||
el("li", t`Simplified event handling with the same events API`),
|
el("li", t`Simplified event handling with the same events API`),
|
||||||
el("li", t`Clean component lifecycle management`),
|
el("li", t`Clean component lifecycle management`),
|
||||||
el("li", t`Improved code organization with scopes`)
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
el(code, { src: fileURL("./components/examples/customElement/intro.js"), page_id }),
|
),
|
||||||
|
el(code, { src: fileURL("./components/examples/customElement/intro.js") }),
|
||||||
|
|
||||||
el(h3, t`Getting Started: Web Components Basics`),
|
el(h3, t`Getting Started: Web Components Basics`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Web Components are a set of standard browser APIs that let you create custom HTML elements with
|
Web Components are a set of standard browser APIs that let you create custom HTML elements with
|
||||||
encapsulated functionality. They consist of three main technologies:
|
encapsulated functionality. They consist of three main technologies:
|
||||||
`),
|
`),
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Custom Elements:")} Create your own HTML tags with JS-defined behavior
|
${el("strong", "Custom Elements:")} Create your own HTML tags with JS-defined behavior
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Shadow DOM:")} Encapsulate styles and markup within a component
|
${el("strong", "Shadow DOM:")} Encapsulate styles and markup within a component
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "HTML Templates:")} Define reusable markup structures
|
${el("strong", "HTML Templates:")} Define reusable markup structures (${el("em",
|
||||||
|
"the dd<el> replaces this part")})
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Let's start with a basic Custom Element example without DDE to establish the foundation:
|
Let’s start with a basic Custom Element example without dd<el> to establish the foundation:
|
||||||
`),
|
`),
|
||||||
el(code, { src: fileURL("./components/examples/customElement/native-basic.js"), page_id }),
|
el(code, { src: fileURL("./components/examples/customElement/native-basic.js") }),
|
||||||
|
|
||||||
el("div", { className: "note" }).append(
|
el("div", { className: "note" }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
For complete information on Web Components, see the
|
For complete information on Web Components, see the
|
||||||
${el("a", references.mdn_custom_elements).append(el("strong", t`MDN documentation`))}.
|
${el("a", references.mdn_custom_elements).append(el("strong", t`MDN documentation`))}.
|
||||||
Also, ${el("a", references.custom_elements_tips).append(el("strong", t`Handy Custom Elements Patterns`))}
|
Also, ${el("a", references.custom_elements_tips).append(el("strong", t`Handy Custom Elements Patterns`))}
|
||||||
@ -101,159 +105,163 @@ export function page({ pkg, info }){
|
|||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`DDE Integration: Step 1 - Event Handling`),
|
el(h3, t`dd<el> Integration: Step 1 - Event Handling`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
The first step in integrating DDE with Web Components is enabling DDE's event system to work with your
|
The first step in integrating dd<el> with Web Components is enabling dd<el>’s event system to work with your
|
||||||
Custom Elements. This is done with ${el("code", "customElementWithDDE")}, which makes your Custom Element
|
Custom Elements. This is done with ${el("code", "customElementWithDDE")}, which makes your Custom Element
|
||||||
compatible with DDE's event handling.
|
compatible with dd<el>’s event handling. (${el("em").append(T`Notice that customElementWithDDE is
|
||||||
|
actually`)} ${el("a", { textContent: "decorator", ...references.decorators })})
|
||||||
`),
|
`),
|
||||||
el("div", { className: "function-table" }).append(
|
el("div", { className: "function-table" }).append(
|
||||||
el("h4", t`customElementWithDDE`),
|
el("h4", t`customElementWithDDE`),
|
||||||
el("dl").append(
|
el("dl").append(
|
||||||
el("dt", t`Purpose`),
|
el("dt", t`Purpose`),
|
||||||
el("dd", t`Enables DDE's event system to work with your Custom Element`),
|
el("dd", t`Enables dd<el>’s event system to work with your Custom Element`),
|
||||||
el("dt", t`Usage`),
|
el("dt", t`Usage`),
|
||||||
el("dd", t`customElementWithDDE(YourElementClass)`),
|
el("dd", t`customElementWithDDE(YourElementClass)`),
|
||||||
el("dt", t`Benefits`),
|
el("dt", t`Benefits`),
|
||||||
el("dd", t`Allows using on.connected(), on.disconnected(), etc. with your element`)
|
el("dd", t`Allows using on.connected(), on.disconnected() or S.observedAttributes().`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/customElement/customElementWithDDE.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/customElement/customElementWithDDE.js") }),
|
||||||
|
|
||||||
el("div", { className: "tip" }).append(
|
el("div", { className: "tip" }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
${el("strong", "Key Point:")} The ${el("code", "customElementWithDDE")} function adds event dispatching
|
${el("strong", "Key Point:")} The ${el("code", "customElementWithDDE")} function adds event dispatching
|
||||||
to your Custom Element lifecycle methods, making them work seamlessly with DDE's event system.
|
to your Custom Element lifecycle methods, making them work seamlessly with dd<el>’s event system.
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`DDE Integration: Step 2 - Rendering Components`),
|
el(h3, t`dd<el> Integration: Step 2 - Rendering Components`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
The next step is to use DDE's component rendering within your Custom Element. This is done with
|
The next step is to use dd<el>’s component rendering within your Custom Element. This is done with
|
||||||
${el("code", "customElementRender")}, which connects your DDE component function to the Custom Element.
|
${el("code", "customElementRender")}, which connects your dd<el> component function to the Custom Element.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "function-table" }).append(
|
el("div", { className: "function-table" }).append(
|
||||||
el("h4", t`customElementRender`),
|
el("h4", t`customElementRender`),
|
||||||
el("dl").append(
|
el("dl").append(
|
||||||
el("dt", t`Purpose`),
|
el("dt", t`Purpose`),
|
||||||
el("dd", t`Connects a DDE component function to a Custom Element`),
|
el("dd", t`Connects a dd<el> component function to a Custom Element`),
|
||||||
el("dt", t`Parameters`),
|
el("dt", t`Parameters`),
|
||||||
el("dd").append(
|
el("dd").append(
|
||||||
el("ol").append(
|
el("ol").append(
|
||||||
el("li", t`Target (usually this or this.shadowRoot)`),
|
el("li", t`Target (usually this or this.shadowRoot)`),
|
||||||
el("li", t`Component function that returns a DOM tree`),
|
el("li", t`Component function that returns a DOM tree`),
|
||||||
el("li", t`Optional: Attributes transformer function (default or S.observedAttributes)`)
|
el("li", t`Optional: Attributes transformer function (empty by default or
|
||||||
|
S.observedAttributes)`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el("dt", t`Returns`),
|
el("dt", t`Returns`),
|
||||||
el("dd", t`The rendered DOM tree`)
|
el("dd", t`The rendered DOM tree`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/customElement/dde.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/customElement/dde.js") }),
|
||||||
|
|
||||||
el("div", { className: "note" }).append(
|
el("div", { className: "note" }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
In this example, we're using Shadow DOM (${el("code", "this.attachShadow()")}) for encapsulation,
|
In this example, we’re using Shadow DOM (${el("code", "this.attachShadow()")}) for encapsulation,
|
||||||
but you can also render directly to the element with ${el("code", "customElementRender(this, ...)")}.
|
but you can also render directly to the element with ${el("code", "customElementRender(this, ...)")}.
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`Reactive Web Components with Signals`),
|
el(h3, t`Reactive Web Components with Signals`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
One of the most powerful features of integrating DDE with Web Components is connecting HTML attributes
|
One of the most powerful features of integrating dd<el> with Web Components is connecting HTML attributes
|
||||||
to DDE's reactive signals system. This creates truly reactive custom elements.
|
to dd<el>’s reactive signals system. This creates truly reactive custom elements.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "tip" }).append(
|
el("div", { className: "tip" }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
${el("strong", "Two Ways to Handle Attributes:")}
|
${el("strong", "Two Ways to Handle Attributes:")}
|
||||||
`),
|
`),
|
||||||
el("ol").append(
|
el("ol").append(
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("code", "observedAttributes")} - Passes attributes as regular values (static)
|
Using standard attribute access (${el("code", "this.getAttribute(<name>)")}) - Passes attributes as
|
||||||
|
regular values (static)
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("code", "S.observedAttributes")} - Transforms attributes into signals (reactive)
|
${el("code", "S.observedAttributes")} - Transforms attributes into signals (reactive)
|
||||||
`)
|
`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Using ${el("code", "S.observedAttributes")} creates a reactive connection between your element's attributes
|
Using the ${el("code", "S.observedAttributes")} creates a reactive connection between your element’s
|
||||||
and its internal rendering. When attributes change, your component automatically updates!
|
attributes and its internal rendering. When attributes change, your component automatically updates!
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/customElement/observedAttributes.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/customElement/observedAttributes.js") }),
|
||||||
|
|
||||||
el("div", { className: "callout" }).append(
|
el("div", { className: "callout" }).append(
|
||||||
el("h4", t`How S.observedAttributes Works`),
|
el("h4", t`How S.observedAttributes Works`),
|
||||||
el("ol").append(
|
el("ol").append(
|
||||||
el("li", t`Takes each attribute listed in static observedAttributes`),
|
el("li", t`Takes each attribute listed in static observedAttributes`),
|
||||||
el("li", t`Creates a DDE signal for each one`),
|
el("li", t`Creates a dd<el> signal for each one`),
|
||||||
el("li", t`Automatically updates these signals when attributes change`),
|
el("li", t`Automatically updates these signals when attributes change`),
|
||||||
el("li", t`Passes the signals to your component function`),
|
el("li", t`Passes the signals to your component function`),
|
||||||
|
el("li", t`In opposite, updates of signals trigger attribute changes`),
|
||||||
el("li", t`Your component reacts to changes through signal subscriptions`)
|
el("li", t`Your component reacts to changes through signal subscriptions`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`Working with Shadow DOM`),
|
el(h3, t`Working with Shadow DOM`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Shadow DOM provides encapsulation for your component's styles and markup. When using DDE with Shadow DOM,
|
Shadow DOM provides encapsulation for your component’s styles and markup. When using dd<el> with Shadow DOM,
|
||||||
you get the best of both worlds: encapsulation plus declarative DOM creation.
|
you get the best of both worlds: encapsulation plus declarative DOM creation.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "illustration" }).append(
|
el("div", { className: "illustration" }).append(
|
||||||
el("h4", t`Shadow DOM Encapsulation`),
|
el("h4", t`Shadow DOM Encapsulation`),
|
||||||
el("pre").append(el("code", `
|
el(pre, { content: `
|
||||||
<my-custom-element>
|
<my-custom-element>
|
||||||
┌─────────────────────────┐
|
┌─────────────────────────┐
|
||||||
#shadow-root
|
#shadow-root
|
||||||
|
|
||||||
Created with DDE:
|
Created with dd<el>
|
||||||
┌──────────────────┐
|
┌──────────────────┐
|
||||||
<div>
|
<div>
|
||||||
<h2>Title</h2>
|
<h2>Title</h2>
|
||||||
<p>Content</p>
|
<p>Content</p>
|
||||||
`))
|
` })
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js") }),
|
||||||
|
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
For more information on Shadow DOM, see
|
For more information on Shadow DOM, see
|
||||||
${el("a", { textContent: t`Using Shadow DOM`, ...references.mdn_shadow_dom_depth })}, or the comprehensive
|
${el("a", { textContent: t`Using Shadow DOM`, ...references.mdn_shadow_dom_depth })}, or the comprehensive
|
||||||
${el("a", { textContent: t`Shadow DOM in Depth`, ...references.shadow_dom_depth })}.
|
${el("a", { textContent: t`Shadow DOM in Depth`, ...references.shadow_dom_depth })}.
|
||||||
`),
|
`),
|
||||||
|
|
||||||
el(h3, t`Working with Slots`),
|
el(h3, t`Working with Slots`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Besides the encapsulation, the Shadow DOM allows for using the ${el("a", references.mdn_shadow_dom_slot).append(
|
Besides the encapsulation, the Shadow DOM allows for using the ${el("a", references.mdn_shadow_dom_slot).append(
|
||||||
el("strong", t`<slot>`), t` element(s)`)}. You can simulate this feature using ${el("code", "simulateSlots")}:
|
el("strong", t`<slot>`), t` element(s)`)}. You can simulate this feature using ${el("code", "simulateSlots")}:
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/customElement/simulateSlots.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/customElement/simulateSlots.js") }),
|
||||||
el("div", { className: "function-table" }).append(
|
el("div", { className: "function-table" }).append(
|
||||||
el("h4", t`simulateSlots`),
|
el("h4", t`simulateSlots`),
|
||||||
el("dl").append(
|
el("dl").append(
|
||||||
el("dt", t`Purpose`),
|
el("dt", t`Purpose`),
|
||||||
el("dd", t`Provides slot functionality when you cannot/do not want to use shadow DOM`),
|
el("dd", t`Provides slot functionality when you cannot/do not want to use shadow DOM`),
|
||||||
el("dt", t`Parameters`),
|
el("dt", t`Parameters`),
|
||||||
el("dd", t`A mapping object of slot names to DOM elements`)
|
el("dd", t`A mapping object of slot names to DOM elements`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`Best Practices for Web Components with DDE`),
|
el(h3, t`Best Practices for Web Components with dd<el>`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
When combining DDE with Web Components, follow these recommendations:
|
When combining dd<el> with Web Components, follow these recommendations:
|
||||||
`),
|
`),
|
||||||
el("ol").append(
|
el("ol").append(
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Always use customElementWithDDE")} to enable event integration
|
${el("strong", "Always use customElementWithDDE")} to enable event integration
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Prefer S.observedAttributes")} for reactive attribute connections
|
${el("strong", "Prefer S.observedAttributes")} for reactive attribute connections
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Create reusable component functions")} that your custom elements render
|
${el("strong", "Create reusable component functions")} that your custom elements render
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Use scope.host()")} to clean up event listeners and subscriptions
|
${el("strong", "Use scope.host()")} to clean up event listeners and subscriptions
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Add setters and getters")} for better property access to your element
|
${el("strong", "Add setters and getters")} for better property access to your element
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
@ -264,7 +272,7 @@ export function page({ pkg, info }){
|
|||||||
el("dt", t`Events not firing properly`),
|
el("dt", t`Events not firing properly`),
|
||||||
el("dd", t`Make sure you called customElementWithDDE before defining the element`),
|
el("dd", t`Make sure you called customElementWithDDE before defining the element`),
|
||||||
el("dt", t`Attributes not updating`),
|
el("dt", t`Attributes not updating`),
|
||||||
el("dd", t`Check that you've properly listed them in static observedAttributes`),
|
el("dd", t`Check that you’ve properly listed them in static observedAttributes`),
|
||||||
el("dt", t`Component not rendering`),
|
el("dt", t`Component not rendering`),
|
||||||
el("dd", t`Verify customElementRender is called in connectedCallback, not constructor`)
|
el("dd", t`Verify customElementRender is called in connectedCallback, not constructor`)
|
||||||
)
|
)
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { T, t } from "./utils/index.js";
|
import { T, t } from "./utils/index.js";
|
||||||
export const info= {
|
export const info= {
|
||||||
title: t`Debugging`,
|
title: t`Debugging`,
|
||||||
fullTitle: t`Debugging applications with deka-dom-el`,
|
fullTitle: t`Debugging applications with dd<el>`,
|
||||||
description: t`Techniques for debugging applications using deka-dom-el, especially signals.`,
|
description: t`Techniques for debugging applications using dd<el>, especially signals.`,
|
||||||
};
|
};
|
||||||
|
|
||||||
import { el } from "deka-dom-el";
|
import { el } from "deka-dom-el";
|
||||||
@ -15,178 +15,223 @@ const fileURL= url=> new URL(url, import.meta.url);
|
|||||||
|
|
||||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||||
export function page({ pkg, info }){
|
export function page({ pkg, info }){
|
||||||
const page_id= info.id;
|
|
||||||
return el(simplePage, { info, pkg }).append(
|
return el(simplePage, { info, pkg }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Debugging is an essential part of application development. This guide provides techniques
|
Debugging is an essential part of application development. This guide provides techniques
|
||||||
and best practices for debugging applications built with deka-dom-el, with a focus on signals.
|
and best practices for debugging applications built with dd<el>, with a focus on signals.
|
||||||
`),
|
`),
|
||||||
|
|
||||||
el(h3, t`Debugging signals`),
|
el(h3, t`Debugging signals`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Signals are reactive primitives that update the UI when their values change. When debugging signals,
|
Signals are reactive primitives that update the UI when their values change. When debugging signals,
|
||||||
you need to track their values, understand their dependencies, and identify why updates are or aren't happening.
|
you need to track their values, understand their dependencies, and identify why updates are or aren’t
|
||||||
|
happening.
|
||||||
`),
|
`),
|
||||||
|
|
||||||
el("h4", t`Inspecting signal values`),
|
el("h4", t`Inspecting signal values`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
The simplest way to debug a signal is to log its current value by calling the get method:
|
The simplest way to debug a signal is to log its current value by calling the get or valueOf method:
|
||||||
`),
|
`),
|
||||||
el(code, { content: `
|
el(code, { content: `
|
||||||
const signal = S(0);
|
const signal = S(0);
|
||||||
console.log('Current value:', signal.get());
|
|
||||||
// without triggering updates
|
|
||||||
console.log('Current value:', signal.valueOf());
|
console.log('Current value:', signal.valueOf());
|
||||||
`, page_id }),
|
`, language: "js" }),
|
||||||
el("p").append(...T`
|
el("div", { className: "warning" }).append(
|
||||||
You can also monitor signal changes by adding a listener:
|
el("p").append(T`
|
||||||
|
${el("code", "signal.get")} is OK, but in some situations may lead to unexpected results:
|
||||||
`),
|
`),
|
||||||
el(code, {
|
el(code, { content: `
|
||||||
content:
|
const signal = S(0);
|
||||||
"// Log every time the signal changes\nS.on(signal, value => console.log('Signal changed:', value));",
|
const derived = S(()=> {
|
||||||
page_id }),
|
console.log('Current value:', signal.get());
|
||||||
|
// ↑ in rare cases this will register unwanted dependency
|
||||||
|
// but typically this is fine ↓
|
||||||
|
return signal.get() + 1;
|
||||||
|
});
|
||||||
|
`, language: "js" })
|
||||||
|
),
|
||||||
|
el("p").append(T`
|
||||||
|
You can also monitor signal changes by adding a listener:
|
||||||
|
`),
|
||||||
|
el(code, { content: `
|
||||||
|
// Log every time the signal changes
|
||||||
|
S.on(signal, value => console.log('Signal changed:', value));
|
||||||
|
`, language: "js" }),
|
||||||
|
|
||||||
el("h4", t`Debugging derived signals`),
|
el("h4", t`Debugging derived signals`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
With derived signals (created with ${el("code", "S(() => computation))")}), debugging is a bit more complex
|
With derived signals (created with ${el("code", "S(() => computation))")}), debugging is a bit more complex
|
||||||
because the value depends on other signals. To understand why a derived signal isn't updating correctly:
|
because the value depends on other signals. To understand why a derived signal isn’t updating correctly:
|
||||||
`),
|
`),
|
||||||
el("ol").append(
|
el("ol").append(
|
||||||
el("li", t`Check that all dependency signals are updating correctly`),
|
el("li", t`Check that all dependency signals are updating correctly`),
|
||||||
el("li", t`Add logging inside the computation function to see when it runs`),
|
el("li", t`Add logging/debugger inside the computation function to see when it runs`),
|
||||||
el("li", t`Verify that the computation function actually accesses the signal values with .get()`)
|
el("li", t`Verify that the computation function actually accesses the signal values with .get()`)
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/debugging/consoleLog.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/debugging/consoleLog.js") }),
|
||||||
|
|
||||||
el(h3, t`Common signal debugging issues`),
|
el("h4", t`Examining signal via DevTools`),
|
||||||
el("h4", t`Signal updates not triggering UI changes`),
|
el("p").append(T`
|
||||||
el("p").append(...T`
|
${el("code", "<signal>.__dde_signal")} - A Symbol property used to identify and store the internal state of
|
||||||
If signal updates aren't reflected in the UI, check:
|
|
||||||
`),
|
|
||||||
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(code, { src: fileURL("./components/examples/debugging/mutations.js"), page_id }),
|
|
||||||
|
|
||||||
el("h4", t`Memory leaks with signal listeners`),
|
|
||||||
el("p").append(...T`
|
|
||||||
Signal listeners can cause memory leaks if not properly cleaned up. Always use AbortSignal
|
|
||||||
to cancel listeners.
|
|
||||||
`),
|
|
||||||
|
|
||||||
el("h4", t`Performance issues with frequently updating signals`),
|
|
||||||
el("p").append(...T`
|
|
||||||
If you notice performance issues with signals that update very frequently:
|
|
||||||
`),
|
|
||||||
el("ul").append(
|
|
||||||
el("li", t`Consider debouncing or throttling signal updates`),
|
|
||||||
el("li", t`Make sure derived signals don't perform expensive calculations unnecessarily`),
|
|
||||||
el("li", t`Keep signal computations focused and minimal`)
|
|
||||||
),
|
|
||||||
el(code, { src: fileURL("./components/examples/debugging/debouncing.js"), page_id }),
|
|
||||||
|
|
||||||
el(h3, t`Browser DevTools tips for deka-dom-el`),
|
|
||||||
el("p").append(...T`
|
|
||||||
When debugging in the browser, deka-dom-el provides several helpful DevTools-friendly features:
|
|
||||||
`),
|
|
||||||
|
|
||||||
el("h4", t`Identifying components in the DOM`),
|
|
||||||
el("p").append(...T`
|
|
||||||
deka-dom-el marks components in the DOM with special comment nodes to help you identify component boundaries.
|
|
||||||
Components created with ${el("code", "el(ComponentFunction)")} are marked with comment nodes
|
|
||||||
${el("code", "<!--<dde:mark type=\"component\" name=\"MyComponent\" host=\"parentElement\"/>-->")} and
|
|
||||||
includes:
|
|
||||||
`),
|
|
||||||
el("ul").append(
|
|
||||||
el("li", "type - Identifies the type of marker (\"component\", \"reactive\", or \"later\")"),
|
|
||||||
el("li", "name - The name of the component function"),
|
|
||||||
el("li", "host - Indicates whether the host is \"this\" (for DocumentFragments) or \"parentElement\""),
|
|
||||||
),
|
|
||||||
|
|
||||||
el("h4", t`Finding reactive elements in the DOM`),
|
|
||||||
el("p").append(...T`
|
|
||||||
When using ${el("code", "S.el()")}, deka-dom-el creates reactive elements in the DOM
|
|
||||||
that are automatically updated when signal values change. These elements are wrapped in special
|
|
||||||
comment nodes for debugging (to be true they are also used internally, so please do not edit them by hand):
|
|
||||||
`),
|
|
||||||
el(code, { src: fileURL("./components/examples/debugging/dom-reactive-mark.js"), page_id }),
|
|
||||||
el("p").append(...T`
|
|
||||||
This is particularly useful when debugging why a reactive section isn't updating as expected.
|
|
||||||
You can inspect the elements between the comment nodes to see their current state and the
|
|
||||||
signal connections through \`__dde_reactive\` of the host element.
|
|
||||||
`),
|
|
||||||
|
|
||||||
el("h4", t`DOM inspection properties`),
|
|
||||||
el("p").append(...T`
|
|
||||||
Elements created with the deka-dom-el library have special properties to aid in debugging:
|
|
||||||
`),
|
|
||||||
el("p").append(...T`
|
|
||||||
${el("code", "<element>.__dde_reactive")} - An array property on DOM elements that tracks signal-to-element
|
|
||||||
relationships. This allows you to quickly identify which elements are reactive and what signals they're
|
|
||||||
bound to. Each entry in the array contains:
|
|
||||||
`),
|
|
||||||
el("ul").append(
|
|
||||||
el("li", "A pair of signal and listener function: [signal, listener]"),
|
|
||||||
el("li", "Additional context information about the element or attribute"),
|
|
||||||
el("li", "Automatically managed by signal.el(), signal.observedAttributes(), and processReactiveAttribute()")
|
|
||||||
),
|
|
||||||
el("p").append(...T`
|
|
||||||
These properties make it easier to understand the reactive structure of your application when inspecting elements.
|
|
||||||
`),
|
|
||||||
el(example, { src: fileURL("./components/examples/signals/debugging-dom.js"), page_id }),
|
|
||||||
|
|
||||||
el("h4", t`Examining signal connections`),
|
|
||||||
el("p").append(...T`
|
|
||||||
${el("code", "<signal>.__dde_signal")} - A Symbol property used to identify and store the internal state of
|
|
||||||
signal objects. It contains the following information:
|
signal objects. It contains the following information:
|
||||||
`),
|
`),
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
el("li", "listeners: A Set of functions called when the signal value changes"),
|
// TODO: value?
|
||||||
el("li", "actions: Custom actions that can be performed on the signal"),
|
el("li", t`listeners: A Set of functions called when the signal value changes`),
|
||||||
el("li", "onclear: Functions to run when the signal is cleared"),
|
el("li", t`actions: Custom actions that can be performed on the signal`),
|
||||||
el("li", "host: Reference to the host element/scope"),
|
el("li", t`onclear: Functions to run when the signal is cleared`),
|
||||||
el("li", "defined: Stack trace information for debugging"),
|
el("li", t`host: Reference to the host element/scope in which the signal was created`),
|
||||||
el("li", "readonly: Boolean flag indicating if the signal is read-only")
|
|
||||||
),
|
),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
…to determine the current value of the signal, call ${el("code", "signal.valueOf()")}.
|
…to determine the current value of the signal, call ${el("code", "signal.valueOf()")}. Don’t hesitate to
|
||||||
`),
|
use the debugger to inspect the signal object.
|
||||||
el("p").append(...T`
|
|
||||||
You can inspect (host) element relationships and bindings with signals in the DevTools console using
|
|
||||||
${el("code", "$0.__dde_reactive")} (for currently selected element). In the console you will see a list of
|
|
||||||
${el("code", "[ [ signal, listener ], element, property ]")}, where:
|
|
||||||
`),
|
|
||||||
el("ul").append(
|
|
||||||
el("li", "signal — the signal triggering the changes"),
|
|
||||||
el("li", "listener — the listener function (this is an internal function for DDE)"),
|
|
||||||
el("li", "element — the DOM element that is bound to the signal"),
|
|
||||||
el("li", "property — the attribute or property name which is changing based on the signal"),
|
|
||||||
),
|
|
||||||
el("p").append(...T`
|
|
||||||
…the structure of \`__dde_reactive\` utilizes the browser's behavior of packing the first field,
|
|
||||||
so you can see the element and property that changes in the console right away.
|
|
||||||
`),
|
`),
|
||||||
|
|
||||||
el("h4", t`Debugging with breakpoints`),
|
el("h4", t`Debugging with breakpoints`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Effective use of breakpoints can help track signal flow:
|
Effective use of breakpoints can help track signal flow:
|
||||||
`),
|
`),
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
Set breakpoints in signal update methods to track when values change
|
Set breakpoints in signal update methods to track when values change
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
Use conditional breakpoints to only break when specific signals change to certain values
|
Use conditional breakpoints to only break when specific signals change to certain values
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
Set breakpoints in your signal computation functions to see when derived signals recalculate
|
Set breakpoints in your signal computation functions to see when derived signals recalculate
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
Use performance profiling to identify bottlenecks in signal updates
|
Use performance profiling to identify bottlenecks in signal updates
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
|
|
||||||
|
el(h3, t`Common signal debugging issues`),
|
||||||
|
el("h4", t`Signal updates not triggering UI changes`),
|
||||||
|
el("p", t`If signal updates aren’t reflected in the UI, check:`),
|
||||||
|
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").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") }),
|
||||||
|
|
||||||
|
el("h4", t`Memory leaks with signal listeners`),
|
||||||
|
el("p").append(T`
|
||||||
|
Signal listeners can cause memory leaks if not properly cleaned up. Always use AbortSignal
|
||||||
|
to cancel listeners when they are used ouside the dd<el> knowledge (el, assign, S.el, … auto cleanup
|
||||||
|
unnecessarily signals automatically).
|
||||||
|
`),
|
||||||
|
|
||||||
|
el("h4", t`Performance issues with frequently updating signals`),
|
||||||
|
el("p", t`If you notice performance issues with signals that update very frequently:`),
|
||||||
|
el("ul").append(
|
||||||
|
el("li", t`Consider debouncing or throttling signal updates`),
|
||||||
|
el("li", t`Make sure derived signals don’t perform expensive calculations unnecessarily`),
|
||||||
|
el("li", t`Keep signal computations focused and minimal`)
|
||||||
|
),
|
||||||
|
el(code, { src: fileURL("./components/examples/debugging/debouncing.js") }),
|
||||||
|
|
||||||
|
el(h3, t`Browser DevTools tips for components and reactivity`),
|
||||||
|
el("p").append(T`
|
||||||
|
When debugging in the browser, dd<el> provides several helpful DevTools-friendly features:
|
||||||
|
`),
|
||||||
|
|
||||||
|
el("h4", t`Finding reactive elements in the DOM`),
|
||||||
|
el("p").append(T`
|
||||||
|
When using ${el("code", "S.el()")}, dd<el> creates reactive elements in the DOM
|
||||||
|
that are automatically updated when signal values change. These elements are wrapped in special
|
||||||
|
comment nodes for debugging (to be true they are also used internally, so please do not edit them by hand):
|
||||||
|
`),
|
||||||
|
el(code, { src: fileURL("./components/examples/debugging/dom-reactive-mark.html") }),
|
||||||
|
el("p").append(T`
|
||||||
|
This is particularly useful when debugging why a reactive section isn’t updating as expected.
|
||||||
|
You can inspect the elements between the comment nodes to see their current state and the
|
||||||
|
signal connections through \`__dde_reactive\` of the host element.
|
||||||
|
`),
|
||||||
|
|
||||||
|
el("h4", t`Identifying components in the DOM`),
|
||||||
|
el("p").append(T`
|
||||||
|
dd<el> marks components in the DOM with special comment nodes to help you identify component boundaries.
|
||||||
|
Components created with ${el("code", "el(MyComponent)")} are marked with comment nodes
|
||||||
|
${el("code", `<!--<dde:mark type="component" name="MyComponent" host="parentElement"/>-->`)} and
|
||||||
|
includes:
|
||||||
|
`),
|
||||||
|
el("ul").append(
|
||||||
|
el("li", t`type - Identifies the type of marker ("component", "reactive", …)`),
|
||||||
|
el("li", t`name - The name of the component function`),
|
||||||
|
el("li", t`host - Indicates whether the host is "this" (for DocumentFragments) or "parentElement"`),
|
||||||
|
),
|
||||||
|
el("div", { className: "warning" }).append(
|
||||||
|
el("p").append(T`
|
||||||
|
There are edge case when the mark can be missing. For example, the (utility) components with reactive
|
||||||
|
keys such as ${el("code", ".textContent")}, ${el("code", ".innerText")} or ${el("code", ".innerHTML")}.
|
||||||
|
As they change the content of the host element.
|
||||||
|
`),
|
||||||
|
el(code, { content: `
|
||||||
|
function Counter() {
|
||||||
|
const count = S(0);
|
||||||
|
return el("button",
|
||||||
|
{ textContent: count, type: "button" },
|
||||||
|
on("click", () => count.set(count.get() + 1)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
`, language: "js" }),
|
||||||
|
),
|
||||||
|
|
||||||
|
el("h4", t`Identifying reactive elements in the DOM`),
|
||||||
|
el("p").append(T`
|
||||||
|
You can inspect (host) element relationships and bindings with signals in the DevTools console using
|
||||||
|
${el("code", "$0.__dde_reactive")} (for currently selected element). In the console you will see a list of
|
||||||
|
${el("code", `[ [ signal, listener ], element, property ]`)}, where:
|
||||||
|
`),
|
||||||
|
el("ul").append(
|
||||||
|
el("li", t`signal — the signal triggering the changes`),
|
||||||
|
el("li", t`listener — the listener function (this is an internal function for dd<el>)`),
|
||||||
|
el("li", t`element — the DOM element that is bound to the signal`),
|
||||||
|
el("li", t`property — the attribute or property name which is changing based on the signal`),
|
||||||
|
),
|
||||||
|
el("p").append(T`
|
||||||
|
…the structure of \`__dde_reactive\` utilizes the browser’s behavior of packing the first field,
|
||||||
|
so you can see the element and property that changes in the console right away. These properties make it
|
||||||
|
easier to understand the reactive structure of your application when inspecting elements.
|
||||||
|
`),
|
||||||
|
el(example, { src: fileURL("./components/examples/signals/debugging-dom.js") }),
|
||||||
|
|
||||||
|
el("p", { className: "note" }).append(T`
|
||||||
|
${el("code", "<element>.__dde_reactive")} - An array property on DOM elements that tracks signal-to-element
|
||||||
|
relationships. This allows you to quickly identify which elements are reactive and what signals they’re
|
||||||
|
bound to. Each entry in the array contains:
|
||||||
|
`),
|
||||||
|
|
||||||
|
el("h4", t`Inspecting events and listeners in DevTools`),
|
||||||
|
el("p").append(T`
|
||||||
|
Modern browser DevTools provide built-in tools for inspecting event listeners attached to DOM elements.
|
||||||
|
For example, in Firefox and Chrome, you can:
|
||||||
|
`),
|
||||||
|
el("ol").append(
|
||||||
|
el("li", t`Select an element in the Elements/Inspector panel`),
|
||||||
|
el("li", t`Look for the "Event Listeners" tab or section`),
|
||||||
|
el("li", t`See all event listeners attached to the element, including those added by dd<el>`)
|
||||||
|
),
|
||||||
|
el("p").append(T`
|
||||||
|
Additionally, dd<el> provides special markers in the DOM that help identify debug information.
|
||||||
|
Look for comments with ${el("code", "dde:mark")}, ${el("code", "dde:disconnected")} and ${el("code",
|
||||||
|
"__dde_reactive")} which indicate components, reactive regions, and other internal relationships:
|
||||||
|
`),
|
||||||
|
el("figure").append(
|
||||||
|
el("img", {
|
||||||
|
src: "./assets/devtools.png",
|
||||||
|
alt: "Screenshot of DevTools showing usage of “event” button to inspect event listeners",
|
||||||
|
}),
|
||||||
|
el("figcaption", t`Firefox DevTools showing dd<el> debugging information with event listeners and reactive
|
||||||
|
markers`)
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { T, t } from "./utils/index.js";
|
import { T, t } from "./utils/index.js";
|
||||||
export const info= {
|
export const info= {
|
||||||
title: t`Extensions and 3rd Party`,
|
title: t`Extensions and 3rd Party`,
|
||||||
fullTitle: t`Extending deka-dom-el with Third-Party Functionalities`,
|
fullTitle: t`Extending dd<el> with Third-Party Functionalities`,
|
||||||
description: t`How to extend deka-dom-el with third-party libraries and custom functionalities.`,
|
description: t`How to extend dd<el> with third-party libraries and custom functionalities.`,
|
||||||
};
|
};
|
||||||
|
|
||||||
import { el } from "deka-dom-el";
|
import { el } from "deka-dom-el";
|
||||||
@ -14,32 +14,31 @@ const fileURL= url=> new URL(url, import.meta.url);
|
|||||||
|
|
||||||
/** @param {import("./types.js").PageAttrs} attrs */
|
/** @param {import("./types.js").PageAttrs} attrs */
|
||||||
export function page({ pkg, info }){
|
export function page({ pkg, info }){
|
||||||
const page_id= info.id;
|
|
||||||
return el(simplePage, { info, pkg }).append(
|
return el(simplePage, { info, pkg }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
deka-dom-el is designed with extensibility in mind. This page covers how to separate
|
dd<el> is designed with extensibility in mind. This page covers how to separate
|
||||||
third-party functionalities and integrate them seamlessly with the library, focusing on
|
third-party functionalities and integrate them seamlessly with the library, focusing on
|
||||||
proper resource cleanup and interoperability.
|
proper resource cleanup and interoperability.
|
||||||
`),
|
`),
|
||||||
|
|
||||||
el(h3, t`DOM Element Extensions with Addons`),
|
el(h3, t`DOM Element Extensions with Addons`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
The primary method for extending DOM elements in deka-dom-el is through the Addon pattern.
|
The primary method for extending DOM elements in dd<el> is through the Addon pattern.
|
||||||
Addons are functions that take an element and applying some functionality to it. This pattern enables a
|
Addons are functions that take an element and applying some functionality to it. This pattern enables
|
||||||
clean, functional approach to element enhancement.
|
a clean, functional approach to element enhancement.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "callout" }).append(
|
el("div", { className: "callout" }).append(
|
||||||
el("h4", t`What are Addons?`),
|
el("h4", t`What are Addons?`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Addons are simply functions with the signature: (element) => void. They:
|
Addons are simply functions with the signature: (element) => void. They:
|
||||||
`),
|
`),
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
el("li", t`Accept a DOM element as input`),
|
el("li", t`Accept a DOM element as input`),
|
||||||
el("li", t`Apply some behavior, property, or attribute to the element`),
|
el("li", t`Apply some behavior, property, or attribute to the element`),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el(code, { content: `
|
el(code, { content: `
|
||||||
// Basic structure of an addon
|
// Basic structure of an addon
|
||||||
function myAddon(config) {
|
function myAddon(config) {
|
||||||
return function(element) {
|
return function(element) {
|
||||||
// Apply functionality to element
|
// Apply functionality to element
|
||||||
@ -47,20 +46,20 @@ function myAddon(config) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using an addon
|
// Using an addon
|
||||||
el("div", { id: "example" }, myAddon({ option: "value" }));
|
el("div", { id: "example" }, myAddon({ option: "value" }));
|
||||||
`.trim(), page_id }),
|
`, language: "js" }),
|
||||||
|
|
||||||
el(h3, t`Resource Cleanup with Abort Signals`),
|
el(h3, t`Resource Cleanup with Abort Signals`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
When extending elements with functionality that uses resources like event listeners, timers,
|
When extending elements with functionality that uses resources like event listeners, timers,
|
||||||
or external subscriptions, it's critical to clean up these resources when the element is removed
|
or external subscriptions, it’s critical to clean up these resources when the element is removed
|
||||||
from the DOM. deka-dom-el provides utilities for this through AbortSignal integration.
|
from the DOM. dd<el> provides utilities for this through AbortSignal integration.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "tip" }).append(
|
el("div", { className: "tip" }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
The ${el("code", "on.disconnectedAsAbort")} utility creates an AbortSignal that automatically
|
The ${el("code", "scope.signal")} property creates an AbortSignal that automatically
|
||||||
triggers when an element is disconnected from the DOM, making cleanup much easier to manage.
|
triggers when an element is disconnected from the DOM, making cleanup much easier to manage.
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
el(code, { content: `
|
el(code, { content: `
|
||||||
@ -80,19 +79,17 @@ function externalLibraryAddon(config, signal) {
|
|||||||
}
|
}
|
||||||
// dde component
|
// dde component
|
||||||
function Component(){
|
function Component(){
|
||||||
const { host }= scope;
|
const { signal }= scope;
|
||||||
const signal= on.disconnectedAsAbort(host);
|
|
||||||
return el("div", null, externalLibraryAddon({ option: "value" }, signal));
|
return el("div", null, externalLibraryAddon({ option: "value" }, signal));
|
||||||
}
|
}
|
||||||
`.trim(), page_id }),
|
`, language: "js" }),
|
||||||
|
|
||||||
el(h3, t`Building Library-Independent Extensions`),
|
el(h3, t`Building Library-Independent Extensions`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
When creating extensions, it's a good practice to make them as library-independent as possible.
|
When creating extensions, it’s a good practice to make them as library-independent as possible.
|
||||||
This approach enables better interoperability and future-proofing.
|
This approach enables better interoperability and future-proofing.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "illustration" }).append(
|
el("div", { className: "illustration" }).append(
|
||||||
el("h4", t`Library-Independent vs. Library-Dependent Extension`),
|
|
||||||
el("div", { className: "tabs" }).append(
|
el("div", { className: "tabs" }).append(
|
||||||
el("div", { className: "tab" }).append(
|
el("div", { className: "tab" }).append(
|
||||||
el("h5", t`✅ Library-Independent`),
|
el("h5", t`✅ Library-Independent`),
|
||||||
@ -106,12 +103,12 @@ function enhancementElement({ signal, ...config }) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
`.trim(), page_id })
|
`, language: "js" })
|
||||||
),
|
),
|
||||||
el("div", { className: "tab" }).append(
|
el("div", { className: "tab" }).append(
|
||||||
el("h5", t`⚠️ Library-Dependent`),
|
el("h5", t`⚠️ Library-Dependent`),
|
||||||
el(code, { content: `
|
el(code, { content: `
|
||||||
// Tightly coupled to deka-dom-el
|
// Tightly coupled to dd<el>
|
||||||
function enhancementElement(config) {
|
function enhancementElement(config) {
|
||||||
return function(element) {
|
return function(element) {
|
||||||
// do something
|
// do something
|
||||||
@ -120,27 +117,86 @@ function enhancementElement(config) {
|
|||||||
})(element);
|
})(element);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
`.trim(), page_id })
|
`, language: "js" })
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`Signal Extensions and Future Compatibility`),
|
el(h3, t`Signal Extensions and Factory Patterns`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
Unlike DOM elements, signal functionality in deka-dom-el currently lacks a standardized
|
Unlike DOM elements, signal functionality in dd<el> currently lacks a standardized
|
||||||
way to create library-independent extensions. This is because signals are implemented
|
way to create library-independent extensions. This is because signals are implemented
|
||||||
differently across libraries.
|
differently across libraries.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "note" }).append(
|
el("div", { className: "note" }).append(
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
In the future, JavaScript may include built-in signals through the
|
In the future, JavaScript may include built-in signals through the
|
||||||
${el("a", { href: "https://github.com/tc39/proposal-signals", textContent: "TC39 Signals Proposal" })}.
|
${el("a", { href: "https://github.com/tc39/proposal-signals", textContent: "TC39 Signals Proposal" })}.
|
||||||
deka-dom-el is designed with future compatibility in mind and will hopefully support these
|
dd<el> is designed with future compatibility in mind and will hopefully support these
|
||||||
native signals without breaking changes when they become available.
|
native signals without breaking changes when they become available.
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
el("p").append(...T`
|
|
||||||
For now, when extending signals functionality, focus on clear interfaces and isolation to make
|
el("h4", t`The Signal Factory Pattern`),
|
||||||
|
el("p").append(T`
|
||||||
|
A powerful approach for extending signal functionality is the "Signal Factory" pattern.
|
||||||
|
This approach encapsulates specific behavior in a function that creates and configures a signal.
|
||||||
|
`),
|
||||||
|
el(code, { content: `
|
||||||
|
/**
|
||||||
|
* Creates a signal for managing route state
|
||||||
|
*
|
||||||
|
* @param {typeof S} signal - The signal constructor
|
||||||
|
*/
|
||||||
|
function routerSignal(signal){
|
||||||
|
const initial = location.hash.replace("#", "") || "all";
|
||||||
|
return signal(initial, {
|
||||||
|
/**
|
||||||
|
* Set the current route
|
||||||
|
* @param {"all"|"active"|"completed"} hash - The route to set
|
||||||
|
*/
|
||||||
|
set(hash){
|
||||||
|
location.hash = hash;
|
||||||
|
this.value = hash;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
const pageS = routerSignal(S);
|
||||||
|
|
||||||
|
// Update URL hash and signal value in one operation
|
||||||
|
S.action(pageS, "set", "active");
|
||||||
|
|
||||||
|
// React to signal changes in the UI
|
||||||
|
el("nav").append(
|
||||||
|
el("a", {
|
||||||
|
href: "#",
|
||||||
|
className: S(()=> pageS.get() === "all" ? "selected" : ""),
|
||||||
|
textContent: "All"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
`, language: "js" }),
|
||||||
|
|
||||||
|
el("div", { className: "callout" }).append(
|
||||||
|
el("h4", t`Benefits of Signal Factories`),
|
||||||
|
el("ul").append(
|
||||||
|
el("li", t`Encapsulate related behavior in a single, reusable function`),
|
||||||
|
el("li", t`Create domain-specific signals with custom actions`),
|
||||||
|
el("li", t`Improve maintainability by centralizing similar logic`),
|
||||||
|
el("li", t`Enable better testability by accepting the signal constructor as a parameter`),
|
||||||
|
el("li", t`Create a clear semantic boundary around related state operations`)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
el("p").append(T`
|
||||||
|
Note how the factory accepts the signal constructor as a parameter, making it easier to test
|
||||||
|
and potentially migrate to different signal implementations in the future.
|
||||||
|
`),
|
||||||
|
|
||||||
|
el("h4", t`Other Signal Extension Approaches`),
|
||||||
|
el("p").append(T`
|
||||||
|
For simpler cases, you can also extend signals with clear interfaces and isolation to make
|
||||||
future migration easier.
|
future migration easier.
|
||||||
`),
|
`),
|
||||||
el(code, { content: `
|
el(code, { content: `
|
||||||
@ -153,54 +209,61 @@ function createEnhancedSignal(initialValue) {
|
|||||||
const decrement = () => signal.set(signal.get() - 1);
|
const decrement = () => signal.set(signal.get() - 1);
|
||||||
|
|
||||||
// Return the original signal with added methods
|
// Return the original signal with added methods
|
||||||
return Object.assign(signal, {
|
return { signal, increment, decrement };
|
||||||
increment,
|
|
||||||
decrement
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage
|
// Usage
|
||||||
const counter = createEnhancedSignal(0);
|
const counter = createEnhancedSignal(0);
|
||||||
el("button")({ onclick: () => counter.increment() }, "Increment");
|
el("button", { textContent: "Increment", onclick: () => counter.increment() });
|
||||||
el("div", S.text\`Count: \${counter}\`);
|
el("div", S.text\`Count: \${counter}\`);
|
||||||
`.trim(), page_id }),
|
`, language: "js" }),
|
||||||
|
|
||||||
|
el("div", { className: "tip" }).append(
|
||||||
|
el("p").append(T`
|
||||||
|
When designing signal extensions, consider creating specialized signals for common patterns like:
|
||||||
|
forms, API requests, persistence, animations, or routing. These can significantly reduce
|
||||||
|
boilerplate code in your applications.
|
||||||
|
`)
|
||||||
|
),
|
||||||
|
|
||||||
el(h3, t`Using Signals Independently`),
|
el(h3, t`Using Signals Independently`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
While signals are tightly integrated with DDE's DOM elements, you can also use them independently.
|
While signals are tightly integrated with DDE’s DOM elements, you can also use them independently.
|
||||||
This can be useful when you need reactivity in non-UI code or want to integrate with other libraries.
|
This can be useful when you need reactivity in non-UI code or want to integrate with other libraries.
|
||||||
`),
|
`),
|
||||||
el("p").append(...T`
|
el("p").append(T`
|
||||||
There are two ways to import signals:
|
There are two ways to import signals:
|
||||||
`),
|
`),
|
||||||
el("ol").append(
|
el("ol").append(
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Standard import")}: ${el("code", "import { S } from \"deka-dom-el/signals\";")}
|
${el("strong", "Standard import")}: ${el("code", `import { S } from "deka-dom-el/signals";`)}
|
||||||
— This automatically registers signals with DDE's DOM reactivity system
|
— This automatically registers signals with DDE’s DOM reactivity system
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Independent import")}: ${el("code", "import { S } from \"deka-dom-el/src/signals-lib\";")}
|
${el("strong", "Independent import")}: ${el("code", `import { S } from "deka-dom-el/src/signals-lib";`)}
|
||||||
— This gives you just the signal system without DOM integration
|
— This gives you just the signal system without DOM integration
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
el(code, { content: `// Independent signals without DOM integration
|
el(code, { content: `
|
||||||
import { signal as S, isSignal } from "deka-dom-el/src/signals-lib";
|
// Independent signals without DOM integration
|
||||||
|
import { signal, isSignal } from "deka-dom-el/src/signals-lib";
|
||||||
|
|
||||||
// Create and use signals as usual
|
// Create and use signals as usual
|
||||||
const count = S(0);
|
const count = signal(0);
|
||||||
const doubled = S(() => count.get() * 2);
|
const doubled = signal(() => count.get() * 2);
|
||||||
|
|
||||||
// Subscribe to changes
|
// Subscribe to changes
|
||||||
S.on(count, value => console.log(value));
|
signal.on(count, value => console.log(value));
|
||||||
|
|
||||||
// Update signal value
|
// Update signal value
|
||||||
count.set(5); // Logs: 5
|
count.set(5); // Logs: 5
|
||||||
console.log(doubled.get()); // 10`, page_id }),
|
console.log(doubled.get()); // 10
|
||||||
el("p").append(...T`
|
`, language: "js" }),
|
||||||
|
el("p").append(T`
|
||||||
The independent signals API includes all core functionality (${el("code", "S()")}, ${el("code", "S.on()")},
|
The independent signals API includes all core functionality (${el("code", "S()")}, ${el("code", "S.on()")},
|
||||||
${el("code", "S.action()")}).
|
${el("code", "S.action()")}).
|
||||||
`),
|
`),
|
||||||
el("div", { class: "callout" }).append(
|
el("div", { className: "callout" }).append(
|
||||||
el("h4", t`When to Use Independent Signals`),
|
el("h4", t`When to Use Independent Signals`),
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
el("li", t`For non-UI state management in your application`),
|
el("li", t`For non-UI state management in your application`),
|
||||||
@ -211,23 +274,27 @@ console.log(doubled.get()); // 10`, page_id }),
|
|||||||
|
|
||||||
el(h3, t`Best Practices for Extensions`),
|
el(h3, t`Best Practices for Extensions`),
|
||||||
el("ol").append(
|
el("ol").append(
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Use AbortSignals for cleanup:")} Always implement proper resource cleanup with
|
${el("strong", "Use AbortSignals for cleanup:")} Always implement proper resource cleanup with
|
||||||
${el("code", "on.disconnectedAsAbort")} or similar mechanisms
|
${el("code", "scope.signal")} or similar mechanisms
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Separate core logic from library adaptation:")} Make your core functionality work
|
${el("strong", "Separate core logic from library adaptation:")} Make your core functionality work
|
||||||
with standard DOM APIs when possible
|
with standard DOM APIs when possible
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
|
${el("strong", "Use signal factories for common patterns:")} Create reusable signal factories that encapsulate
|
||||||
|
domain-specific behavior and state logic
|
||||||
|
`),
|
||||||
|
el("li").append(T`
|
||||||
${el("strong", "Document clearly:")} Provide clear documentation on how your extension works
|
${el("strong", "Document clearly:")} Provide clear documentation on how your extension works
|
||||||
and what resources it uses
|
and what resources it uses
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Follow the Addon pattern:")} Keep to the (element) => element signature for
|
${el("strong", "Follow the Addon pattern:")} Keep to the (element) => element signature for
|
||||||
DOM element extensions
|
DOM element extensions
|
||||||
`),
|
`),
|
||||||
el("li").append(...T`
|
el("li").append(T`
|
||||||
${el("strong", "Avoid modifying global state:")} Extensions should be self-contained and not
|
${el("strong", "Avoid modifying global state:")} Extensions should be self-contained and not
|
||||||
affect other parts of the application
|
affect other parts of the application
|
||||||
`)
|
`)
|
||||||
@ -241,14 +308,17 @@ console.log(doubled.get()); // 10`, page_id }),
|
|||||||
are disconnected`),
|
are disconnected`),
|
||||||
|
|
||||||
el("dt", t`Tight coupling with library internals`),
|
el("dt", t`Tight coupling with library internals`),
|
||||||
el("dd", t`Focus on standard DOM APIs and clean interfaces rather than depending on deka-dom-el
|
el("dd", t`Focus on standard DOM APIs and clean interfaces rather than depending on dd<el>
|
||||||
implementation details`),
|
implementation details`),
|
||||||
|
|
||||||
el("dt", t`Mutating element prototypes`),
|
el("dt", t`Mutating element prototypes`),
|
||||||
el("dd", t`Prefer compositional approaches with addons over modifying element prototypes`),
|
el("dd", t`Prefer compositional approaches with addons over modifying element prototypes`),
|
||||||
|
|
||||||
|
el("dt", t`Duplicating similar signal logic across components`),
|
||||||
|
el("dd", t`Use signal factories to encapsulate and reuse related signal behavior`),
|
||||||
|
|
||||||
el("dt", t`Complex initialization in addons`),
|
el("dt", t`Complex initialization in addons`),
|
||||||
el("dd", t`Split complex logic into a separate initialization function that the addon can call`)
|
el("dd", t`Split complex logic into a separate initialization function that the addon can call`)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
400
docs/p09-optimization.html.js
Normal file
400
docs/p09-optimization.html.js
Normal file
@ -0,0 +1,400 @@
|
|||||||
|
import { T, t } from "./utils/index.js";
|
||||||
|
export const info= {
|
||||||
|
title: t`Performance Optimization`,
|
||||||
|
fullTitle: t`Performance Optimization with dd<el>`,
|
||||||
|
description: t`Techniques for optimizing your dd<el> applications, focusing on memoization and efficient rendering.`,
|
||||||
|
};
|
||||||
|
|
||||||
|
import { el } from "deka-dom-el";
|
||||||
|
import { simplePage } from "./layout/simplePage.html.js";
|
||||||
|
import { example } from "./components/example.html.js";
|
||||||
|
import { h3 } from "./components/pageUtils.html.js";
|
||||||
|
import { code } from "./components/code.html.js";
|
||||||
|
import { mnemonic } from "./components/mnemonic/optimization-init.js";
|
||||||
|
/** @param {string} url */
|
||||||
|
const fileURL= url=> new URL(url, import.meta.url);
|
||||||
|
const references= {
|
||||||
|
/** memo documentation */
|
||||||
|
memo_docs: {
|
||||||
|
title: t`dd<el> memo API documentation`,
|
||||||
|
href: "https://github.com/jaandrle/deka-dom-el#memo-api",
|
||||||
|
},
|
||||||
|
/** AbortController */
|
||||||
|
mdn_abort: {
|
||||||
|
title: t`MDN documentation for AbortController`,
|
||||||
|
href: "https://developer.mozilla.org/en-US/docs/Web/API/AbortController",
|
||||||
|
},
|
||||||
|
/** Performance API */
|
||||||
|
mdn_perf: {
|
||||||
|
title: t`MDN documentation for Web Performance API`,
|
||||||
|
href: "https://developer.mozilla.org/en-US/docs/Web/API/Performance_API",
|
||||||
|
},
|
||||||
|
/** Virtual DOM */
|
||||||
|
virtual_dom: {
|
||||||
|
title: t`Virtual DOM concept explanation`,
|
||||||
|
href: "https://reactjs.org/docs/faq-internals.html#what-is-the-virtual-dom",
|
||||||
|
},
|
||||||
|
/** DocumentFragment */
|
||||||
|
mdn_fragment: {
|
||||||
|
title: t`MDN documentation for DocumentFragment`,
|
||||||
|
href: "https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||||
|
export function page({ pkg, info }){
|
||||||
|
return el(simplePage, { info, pkg }).append(
|
||||||
|
el("p").append(T`
|
||||||
|
As your applications grow, performance becomes increasingly important. dd<el> provides several
|
||||||
|
techniques to optimize rendering performance, especially when dealing with large lists or frequently
|
||||||
|
updating components. This guide focuses on memoization and other optimization strategies.
|
||||||
|
`),
|
||||||
|
el("div", { className: "callout" }).append(
|
||||||
|
el("h4", t`dd<el> Performance Optimization: Key Benefits`),
|
||||||
|
el("ul").append(
|
||||||
|
el("li", t`Efficient memoization system for component reuse`),
|
||||||
|
el("li", t`Targeted re-rendering without virtual DOM overhead`),
|
||||||
|
el("li", t`Memory management through AbortSignal integration`),
|
||||||
|
el("li", t`Optimized signal updates for reactive UI patterns`),
|
||||||
|
el("li", t`Simple debugging for performance bottlenecks`)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
el(code, { src: fileURL("./components/examples/optimization/intro.js") }),
|
||||||
|
|
||||||
|
el(h3, t`Memoization with memo: Native vs dd<el>`),
|
||||||
|
el("p").append(T`
|
||||||
|
In standard JavaScript applications, optimizing list rendering often involves manual caching
|
||||||
|
or relying on complex virtual DOM diffing algorithms. dd<el>'s ${el("code", "memo")} function
|
||||||
|
provides a simpler, more direct approach:
|
||||||
|
`),
|
||||||
|
el("div", { className: "illustration" }).append(
|
||||||
|
el("div", { className: "comparison" }).append(
|
||||||
|
el("div").append(
|
||||||
|
el("h5", t`Without Memoization`),
|
||||||
|
el(code, { content: `
|
||||||
|
// Each update to todosArray recreates all elements
|
||||||
|
function renderTodos(todosArray) {
|
||||||
|
return el("ul").append(
|
||||||
|
S.el(todosArray, todos => todos.map(todo=>
|
||||||
|
el("li", {
|
||||||
|
textContent: todo.text,
|
||||||
|
dataState: todo.completed ? "completed" : "",
|
||||||
|
})
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
`, language: "js" })
|
||||||
|
),
|
||||||
|
el("div").append(
|
||||||
|
el("h5", t`With dd<el>'s memo`),
|
||||||
|
el(code, { content: `
|
||||||
|
// With dd<el>’s memoization
|
||||||
|
function renderTodos(todosArray) {
|
||||||
|
return el("ul").append(
|
||||||
|
S.el(todosArray, todos => todos.map(todo=>
|
||||||
|
// Reuses DOM elements when items haven’t changed
|
||||||
|
memo(todo.key, () =>
|
||||||
|
el("li", {
|
||||||
|
textContent: todo.text,
|
||||||
|
dataState: todo.completed ? "completed" : "",
|
||||||
|
})
|
||||||
|
)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
`, language: "js" })
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
el("p").append(T`
|
||||||
|
The ${el("a", references.memo_docs).append(el("code", "memo"))} function in dd<el> allows you to
|
||||||
|
cache and reuse DOM elements instead of recreating them on every render, which can
|
||||||
|
significantly improve performance for components that render frequently or contain heavy computations.
|
||||||
|
`),
|
||||||
|
|
||||||
|
el("p").append(T`
|
||||||
|
The memo system is particularly useful for:
|
||||||
|
`),
|
||||||
|
el("ul").append(
|
||||||
|
el("li", t`Lists that update frequently but where most items remain the same`),
|
||||||
|
el("li", t`Components with expensive rendering operations`),
|
||||||
|
el("li", t`Optimizing signal-driven UI updates`)
|
||||||
|
),
|
||||||
|
|
||||||
|
el(h3, t`Using memo with Signal Rendering`),
|
||||||
|
el("p").append(T`
|
||||||
|
The most common use case for memoization is within ${el("code", "S.el()")} when rendering lists with
|
||||||
|
${el("code", "map()")}:
|
||||||
|
`),
|
||||||
|
el(code, { content: `
|
||||||
|
S.el(todosSignal, todos =>
|
||||||
|
el("ul").append(
|
||||||
|
...todos.map(todo =>
|
||||||
|
// Use a unique identifiers
|
||||||
|
memo(todo.id, () =>
|
||||||
|
el(TodoItem, todo)
|
||||||
|
))))
|
||||||
|
`, language: "js" }),
|
||||||
|
|
||||||
|
el("p").append(T`
|
||||||
|
The ${el("code", "memo")} function in this context:
|
||||||
|
`),
|
||||||
|
el("ol").append(
|
||||||
|
el("li", t`Takes a unique key (todo.id) to identify this item`),
|
||||||
|
el("li", t`Caches the element created by the generator function`),
|
||||||
|
el("li", t`Returns the cached element on subsequent renders if the key remains the same`),
|
||||||
|
el("li", t`Only calls the generator function when rendering an item with a new key`)
|
||||||
|
),
|
||||||
|
|
||||||
|
el(example, { src: fileURL("./components/examples/optimization/memo.js") }),
|
||||||
|
|
||||||
|
el(h3, t`Creating Memoization Scopes`),
|
||||||
|
el("p").append(T`
|
||||||
|
The ${el("code", "memo()")} uses cache store defined via the ${el("code", "memo.scope")} function.
|
||||||
|
That is actually what the ${el("code", "S.el")} is doing under the hood:
|
||||||
|
`),
|
||||||
|
el(code, { content: `
|
||||||
|
import { memo } from "deka-dom-el";
|
||||||
|
|
||||||
|
// Create a memoization scope
|
||||||
|
const renderItem = memo.scope(function(item) {
|
||||||
|
return el().append(
|
||||||
|
el("h3", item.title),
|
||||||
|
el("p", item.description),
|
||||||
|
// Expensive rendering operations...
|
||||||
|
memo(item, ()=> el("div", { className: "expensive-component" }))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use the memoized function
|
||||||
|
const items = [/* array of items */];
|
||||||
|
const container = el("div").append(
|
||||||
|
...items.map(item => renderItem(item))
|
||||||
|
);
|
||||||
|
`, language: "js" }),
|
||||||
|
|
||||||
|
el("p").append(T`
|
||||||
|
The scope function accepts options to customize its behavior:
|
||||||
|
`),
|
||||||
|
el(code, { content: `
|
||||||
|
const renderList = memo.scope(function(list) {
|
||||||
|
return list.map(item =>
|
||||||
|
memo(item.id, () => el(ItemComponent, item))
|
||||||
|
);
|
||||||
|
}, {
|
||||||
|
// Only keep the cache from the most recent render
|
||||||
|
onlyLast: true,
|
||||||
|
|
||||||
|
// Clear cache when signal is aborted
|
||||||
|
signal: controller.signal
|
||||||
|
});
|
||||||
|
`, language: "js" }),
|
||||||
|
el("p").append(T`
|
||||||
|
You can use custom memo scope as function in (e. g. ${el("code", "S.el(signal, renderList)")}) and as
|
||||||
|
(Abort) signal use ${el("code", "scope.signal")}.
|
||||||
|
`),
|
||||||
|
|
||||||
|
el("div", { className: "function-table" }).append(
|
||||||
|
el("dl").append(
|
||||||
|
el("dt", t`onlyLast Option`),
|
||||||
|
el("dd").append(T`Only keeps the cache from the most recent function call,
|
||||||
|
which is useful when the entire collection is replaced. ${el("strong", "This is default behavior of ")
|
||||||
|
.append(el("code", "S.el"))}!`),
|
||||||
|
|
||||||
|
el("dt", t`signal Option`),
|
||||||
|
el("dd").append(T`An ${el("a", references.mdn_abort).append(el("code", "AbortSignal"))}
|
||||||
|
that will clear the cache when aborted, helping with memory management`)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
el(h3, t`Additional Optimization Techniques`),
|
||||||
|
|
||||||
|
el("h4", t`Minimizing Signal Updates`),
|
||||||
|
el("p").append(T`
|
||||||
|
Signals are efficient, but unnecessary updates can impact performance:
|
||||||
|
`),
|
||||||
|
el("ul").append(
|
||||||
|
el("li", t`For frequently updating values (like scroll position), consider debouncing`),
|
||||||
|
el("li", t`Keep signal computations small and focused`),
|
||||||
|
),
|
||||||
|
|
||||||
|
el("h4", t`Optimizing List Rendering`),
|
||||||
|
el("p").append(T`
|
||||||
|
Beyond memoization, consider these approaches for optimizing list rendering:
|
||||||
|
`),
|
||||||
|
el("ul").append(
|
||||||
|
el("li", t`Virtualize long lists to only render visible items`),
|
||||||
|
el("li", t`Use stable, unique keys for list items`),
|
||||||
|
el("li", t`Batch updates to signals that drive large lists`),
|
||||||
|
el("li", t`Consider using a memo scope for the entire list component`)
|
||||||
|
),
|
||||||
|
|
||||||
|
el("div", { className: "tip" }).append(
|
||||||
|
el("p").append(T`
|
||||||
|
Memoization works best when your keys are stable and unique. Use IDs or other persistent
|
||||||
|
identifiers rather than array indices, which can change when items are reordered.
|
||||||
|
`),
|
||||||
|
el("p").append(T`
|
||||||
|
Alternatively you can use any “jsonable” value as key, when the primitive values aren’t enough.
|
||||||
|
`)
|
||||||
|
),
|
||||||
|
|
||||||
|
el("h4", t`Memory Management`),
|
||||||
|
el("p").append(T`
|
||||||
|
To prevent memory leaks and reduce memory consumption:
|
||||||
|
`),
|
||||||
|
el("ul").append(
|
||||||
|
el("li", t`Clear memo caches when components are removed`),
|
||||||
|
el("li", t`Use AbortSignals to manage memo lifetimes`),
|
||||||
|
el("li", t`Call S.clear() on signals that are no longer needed`),
|
||||||
|
el("li", t`Remove event listeners when elements are removed from the DOM`)
|
||||||
|
),
|
||||||
|
|
||||||
|
el("h4", t`Choosing the Right Optimization Approach`),
|
||||||
|
el("p").append(T`
|
||||||
|
While ${el("code", "memo")} is powerful, different scenarios call for different optimization techniques:
|
||||||
|
`),
|
||||||
|
el("div", { className: "function-table" }).append(
|
||||||
|
el("dl").append(
|
||||||
|
el("dt", t`memo`),
|
||||||
|
el("dd").append(T`
|
||||||
|
Best for list rendering where items rarely change or only their properties update.
|
||||||
|
${el("code", "todos.map(todo => memo(todo.id, () => el(TodoItem, todo)))")}
|
||||||
|
Use when you need to cache and reuse DOM elements to avoid recreating them on every render.
|
||||||
|
`),
|
||||||
|
|
||||||
|
el("dt", t`Signal computations`),
|
||||||
|
el("dd").append(T`
|
||||||
|
Ideal for derived values that depend on other signals and need to auto-update.
|
||||||
|
${el("code", "const totalPrice = S(() => items.get().reduce((t, i) => t + i.price, 0))")}
|
||||||
|
Use when calculated values need to stay in sync with changing source data.
|
||||||
|
`),
|
||||||
|
|
||||||
|
el("dt", t`Debouncing/Throttling`),
|
||||||
|
el("dd").append(T`
|
||||||
|
Essential for high-frequency events (scroll, resize) or rapidly changing input values.
|
||||||
|
${el("code", "debounce(e => searchQuery.set(e.target.value), 300)")}
|
||||||
|
Use to limit the rate at which expensive operations execute when triggered by fast events.
|
||||||
|
`),
|
||||||
|
|
||||||
|
el("dt", t`memo.scope`),
|
||||||
|
el("dd").append(T`
|
||||||
|
Useful for using memoization inside any function: ${el("code",
|
||||||
|
"const renderList = memo.scope(items => items.map(...))")}. Use to create isolated memoization
|
||||||
|
contexts that can be cleared or managed independently.
|
||||||
|
`),
|
||||||
|
|
||||||
|
el("dt", t`Stateful components`),
|
||||||
|
el("dd").append(T`
|
||||||
|
For complex UI components with internal state management.
|
||||||
|
${el("code", "el(ComplexComponent, { initialState, onChange })")}
|
||||||
|
Use when a component needs to encapsulate and manage its own state and lifecycle.
|
||||||
|
`)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
el(h3, t`Known Issues and Limitations`),
|
||||||
|
el("p").append(T`
|
||||||
|
While memoization is a powerful optimization technique, there are some limitations and edge cases to be aware of:
|
||||||
|
`),
|
||||||
|
|
||||||
|
el("h4", t`Document Fragments and Memoization`),
|
||||||
|
el("p").append(T`
|
||||||
|
One important limitation to understand is how memoization interacts with
|
||||||
|
${el("a", references.mdn_fragment).append("DocumentFragment")} objects.
|
||||||
|
Functions like ${el("code", "S.el")} internally use DocumentFragment to efficiently handle multiple elements,
|
||||||
|
but this can lead to unexpected behavior with memoization.
|
||||||
|
`),
|
||||||
|
el(code, { content: `
|
||||||
|
// This pattern can lead to unexpected behavior
|
||||||
|
const memoizedFragment = memo("key", () => {
|
||||||
|
// Creates a DocumentFragment internally
|
||||||
|
return S.el(itemsSignal, items => items.map(item => el("div", item)));
|
||||||
|
});
|
||||||
|
|
||||||
|
// After the fragment is appended to the DOM, it becomes empty
|
||||||
|
container.append(memoizedFragment);
|
||||||
|
|
||||||
|
// On subsequent renders, the cached fragment is empty!
|
||||||
|
container.append(memoizedFragment); // Nothing gets appended
|
||||||
|
`, language: "js" }),
|
||||||
|
|
||||||
|
el("p").append(T`
|
||||||
|
This happens because a DocumentFragment is emptied when it's appended to the DOM. When the fragment
|
||||||
|
is cached by memo and reused, it's already empty.
|
||||||
|
`),
|
||||||
|
|
||||||
|
el("div", { className: "tip" }).append(
|
||||||
|
el("h5", t`Solution: Memoize Individual Items`),
|
||||||
|
el(code, { content: `
|
||||||
|
// Correct approach: memoize the individual items, not the fragment
|
||||||
|
S.el(itemsSignal, items => items.map(item =>
|
||||||
|
memo(item.id, () => el("div", item))
|
||||||
|
));
|
||||||
|
|
||||||
|
// Or use a container element instead of relying on a fragment
|
||||||
|
memo("key", () =>
|
||||||
|
el("div", { className: "item-container" }).append(
|
||||||
|
S.el(itemsSignal, items => items.map(item => el("div", item)))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
`, language: "js" })
|
||||||
|
),
|
||||||
|
|
||||||
|
el("p").append(T`
|
||||||
|
Generally, you should either:
|
||||||
|
`),
|
||||||
|
el("ol").append(
|
||||||
|
el("li", t`Memoize individual items within the collection, not the entire collection result`),
|
||||||
|
el("li", t`Wrap the result in a container element instead of relying on fragment behavior`),
|
||||||
|
el("li", t`Be aware that S.el() and similar functions that return multiple elements are using fragments internally`)
|
||||||
|
),
|
||||||
|
|
||||||
|
el("div", { className: "note" }).append(
|
||||||
|
el("p").append(T`
|
||||||
|
This limitation isn't specific to dd<el> but is related to how DocumentFragment works in the DOM.
|
||||||
|
Once a fragment is appended to the DOM, its child nodes are moved from the fragment to the target element,
|
||||||
|
leaving the original fragment empty.
|
||||||
|
`)
|
||||||
|
),
|
||||||
|
|
||||||
|
el(h3, t`Performance Debugging`),
|
||||||
|
el("p").append(T`
|
||||||
|
To identify performance bottlenecks in your dd<el> applications:
|
||||||
|
`),
|
||||||
|
el("ol").append(
|
||||||
|
el("li").append(T`Use ${el("a", references.mdn_perf).append("browser performance tools")} to profile
|
||||||
|
rendering times`),
|
||||||
|
el("li", t`Check for excessive signal updates using S.on() listeners with console.log`),
|
||||||
|
el("li", t`Verify memo usage by inspecting cache hit rates`),
|
||||||
|
el("li", t`Look for components that render more frequently than necessary`)
|
||||||
|
),
|
||||||
|
|
||||||
|
el("div", { className: "note" }).append(
|
||||||
|
el("p").append(T`
|
||||||
|
For more details on debugging, see the ${el("a", { href: "p07-debugging.html", textContent: "Debugging" })} page.
|
||||||
|
`)
|
||||||
|
),
|
||||||
|
|
||||||
|
el(h3, t`Best Practices for Optimized Rendering`),
|
||||||
|
el("ol").append(
|
||||||
|
el("li").append(T`
|
||||||
|
${el("strong", "Use memo for list items:")} Memoize items in lists, especially when they contain complex components.
|
||||||
|
`),
|
||||||
|
el("li").append(T`
|
||||||
|
${el("strong", "Clean up with AbortSignals:")} Connect memo caches to component lifecycles using AbortSignals.
|
||||||
|
`),
|
||||||
|
el("li").append(T`
|
||||||
|
${el("strong", "Profile before optimizing:")} Identify actual bottlenecks before adding optimization.
|
||||||
|
`),
|
||||||
|
el("li").append(T`
|
||||||
|
${el("strong", "Use derived signals:")} Compute derived values efficiently with signal computations.
|
||||||
|
`),
|
||||||
|
el("li").append(T`
|
||||||
|
${el("strong", "Avoid memoizing fragments:")} Memoize individual elements or use container elements
|
||||||
|
instead of DocumentFragments.
|
||||||
|
`)
|
||||||
|
),
|
||||||
|
|
||||||
|
el(mnemonic),
|
||||||
|
);
|
||||||
|
}
|
@ -1,363 +0,0 @@
|
|||||||
import { T, t } from "./utils/index.js";
|
|
||||||
export const info= {
|
|
||||||
title: t`Ireland Components`,
|
|
||||||
fullTitle: t`Interactive Demo Components with Server-Side Pre-Rendering`,
|
|
||||||
description: t`Creating live, interactive component examples in documentation with server-side
|
|
||||||
rendering and client-side hydration.`,
|
|
||||||
};
|
|
||||||
|
|
||||||
import { el } from "deka-dom-el";
|
|
||||||
import { simplePage } from "./layout/simplePage.html.js";
|
|
||||||
import { h3 } from "./components/pageUtils.html.js";
|
|
||||||
import { code } from "./components/code.html.js";
|
|
||||||
import { ireland } from "./components/ireland.html.js";
|
|
||||||
/** @param {string} url */
|
|
||||||
const fileURL= url=> new URL(url, import.meta.url);
|
|
||||||
|
|
||||||
/** @param {import("./types.js").PageAttrs} attrs */
|
|
||||||
export function page({ pkg, info }){
|
|
||||||
const page_id= info.id;
|
|
||||||
return el(simplePage, { info, pkg }).append(
|
|
||||||
el("div", { className: "warning" }).append(
|
|
||||||
el("p").append(...T`
|
|
||||||
This part of the documentation is primarily intended for technical enthusiasts and authors of
|
|
||||||
3rd-party libraries. It describes an advanced feature, not a core part of the library. Most users will
|
|
||||||
not need to implement this functionality directly in their applications. This capability will hopefully
|
|
||||||
be covered by third-party libraries or frameworks that provide simpler SSR integration using
|
|
||||||
deka-dom-el.
|
|
||||||
`)
|
|
||||||
),
|
|
||||||
|
|
||||||
el(h3, t`What Are Ireland Components?`),
|
|
||||||
el("p").append(...T`
|
|
||||||
Ireland components are a special type of documentation component that:
|
|
||||||
`),
|
|
||||||
el("ul").append(
|
|
||||||
el("li", t`Display source code with syntax highlighting`),
|
|
||||||
el("li", t`Pre-render components on the server during documentation build`),
|
|
||||||
el("li", t`Copy component source files to the documentation output`),
|
|
||||||
el("li", t`Provide client-side rehydration for interactive demos`),
|
|
||||||
el("li", t`Allow users to run and experiment with components in real-time`)
|
|
||||||
),
|
|
||||||
|
|
||||||
el(h3, t`How Ireland Components Work`),
|
|
||||||
el("p").append(...T`
|
|
||||||
The Ireland component system consists of several parts working together:
|
|
||||||
`),
|
|
||||||
|
|
||||||
el("ol").append(
|
|
||||||
el("li").append(...T`
|
|
||||||
${el("strong", "Server-side rendering:")} Components are pre-rendered during the documentation build process
|
|
||||||
`),
|
|
||||||
el("li").append(...T`
|
|
||||||
${el("strong", "Component registration:")} Source files are copied to the documentation output directory
|
|
||||||
`),
|
|
||||||
el("li").append(...T`
|
|
||||||
${el("strong", "Client-side scripting:")} JavaScript code is generated to load and render components
|
|
||||||
`),
|
|
||||||
el("li").append(...T`
|
|
||||||
${el("strong", "User interaction:")} The "Run Component" button dynamically loads and renders the component
|
|
||||||
`)
|
|
||||||
),
|
|
||||||
|
|
||||||
el(h3, t`Implementation Architecture`),
|
|
||||||
el("p").append(...T`
|
|
||||||
The core of the Ireland system is implemented in ${el("code", "docs/components/ireland.html.js")}.
|
|
||||||
It integrates with the SSR build process using the ${el("code", "registerClientFile")} function
|
|
||||||
from ${el("code", "docs/ssr.js")}.
|
|
||||||
`),
|
|
||||||
|
|
||||||
el(code, { content: `
|
|
||||||
// Basic usage of an ireland component
|
|
||||||
el(ireland, {
|
|
||||||
src: fileURL("./components/examples/path/to/component.js"),
|
|
||||||
exportName: "NamedExport", // optional, defaults to "default",
|
|
||||||
})`, page_id }),
|
|
||||||
|
|
||||||
el("p").append(...T`
|
|
||||||
During the build process (${el("code", "bs/docs.js")}), the following happens:
|
|
||||||
`),
|
|
||||||
|
|
||||||
el("ol").append(
|
|
||||||
el("li", t`Component source code is loaded and displayed with syntax highlighting`),
|
|
||||||
el("li", t`Source files are registered to be copied to the output directory`),
|
|
||||||
el("li", t`Client-side scripts are generated for each page with ireland components`),
|
|
||||||
el("li", t`The component is wrapped in a UI container with controls`)
|
|
||||||
),
|
|
||||||
|
|
||||||
el(h3, t`Core Implementation Details`),
|
|
||||||
el("p").append(...T`
|
|
||||||
Let's look at the key parts of the ireland component implementation:
|
|
||||||
`),
|
|
||||||
|
|
||||||
el("h4", t`Building SSR`),
|
|
||||||
el(code, { content: `
|
|
||||||
// From bs/docs.js - Server-side rendering engine
|
|
||||||
import { createHTMl } from "./docs/jsdom.js";
|
|
||||||
import { register, queue } from "../jsdom.js";
|
|
||||||
import { path_target, dispatchEvent } from "../docs/ssr.js";
|
|
||||||
|
|
||||||
// For each page, render it on the server
|
|
||||||
for(const { id, info } of pages) {
|
|
||||||
// Create a virtual DOM environment for server-side rendering
|
|
||||||
const serverDOM = createHTMl("");
|
|
||||||
serverDOM.registerGlobally("HTMLScriptElement");
|
|
||||||
|
|
||||||
// Register deka-dom-el with the virtual DOM
|
|
||||||
const { el } = await register(serverDOM.dom);
|
|
||||||
|
|
||||||
// Import and render the page component
|
|
||||||
const { page } = await import(\`../docs/\${id}.html.js\`);
|
|
||||||
serverDOM.document.body.append(
|
|
||||||
el(page, { pkg, info }),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Process the queue of asynchronous operations
|
|
||||||
await queue();
|
|
||||||
|
|
||||||
// Trigger render event handlers
|
|
||||||
dispatchEvent("oneachrender", document);
|
|
||||||
|
|
||||||
// Write the HTML to the output file
|
|
||||||
s.echo(serverDOM.serialize()).to(path_target.root+id+".html");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Final build step - trigger SSR end event
|
|
||||||
dispatchEvent("onssrend");
|
|
||||||
`, page_id }),
|
|
||||||
el("h4", t`File Registration`),
|
|
||||||
el(code, { content: `
|
|
||||||
// From docs/ssr.js - File registration system
|
|
||||||
export function registerClientFile(url, { head, folder = "", replacer } = {}) {
|
|
||||||
// Ensure folder path ends with a slash
|
|
||||||
if(folder && !folder.endsWith("/")) folder += "/";
|
|
||||||
|
|
||||||
// Extract filename from URL
|
|
||||||
const file_name = url.pathname.split("/").pop();
|
|
||||||
|
|
||||||
// Create target directory if needed
|
|
||||||
s.mkdir("-p", path_target.root+folder);
|
|
||||||
|
|
||||||
// Get file content and apply optional replacer function
|
|
||||||
let content = s.cat(url);
|
|
||||||
if(replacer) content = s.echo(replacer(content.toString()));
|
|
||||||
|
|
||||||
// Write content to the output directory
|
|
||||||
content.to(path_target.root+folder+file_name);
|
|
||||||
|
|
||||||
// If a head element was provided, add it to the document
|
|
||||||
if(!head) return;
|
|
||||||
head[head instanceof HTMLScriptElement ? "src" : "href"] = file_name;
|
|
||||||
document.head.append(head);
|
|
||||||
}
|
|
||||||
`, page_id }),
|
|
||||||
el("h4", t`Server-Side Rendering`),
|
|
||||||
el(code, { content: `
|
|
||||||
// From docs/components/ireland.html.js - Server-side component implementation
|
|
||||||
export function ireland({ src, exportName = "default", props = {} }) {
|
|
||||||
// Calculate relative path for imports
|
|
||||||
const path = "./"+relative(dir, src.pathname);
|
|
||||||
|
|
||||||
// Generate unique ID for this component instance
|
|
||||||
const id = "ireland-" + generateComponentId(src);
|
|
||||||
|
|
||||||
// Create placeholder element
|
|
||||||
const element = el.mark({ type: "later", name: ireland.name });
|
|
||||||
|
|
||||||
// Import and render the component during SSR
|
|
||||||
queue(import(path).then(module => {
|
|
||||||
const component = module[exportName];
|
|
||||||
element.replaceWith(el(component, props, mark(id)));
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Register client-side hydration on first component
|
|
||||||
if(!componentsRegistry.size)
|
|
||||||
addEventListener("oneachrender", registerClientPart);
|
|
||||||
|
|
||||||
// Store component info for client-side hydration
|
|
||||||
componentsRegistry.set(id, {
|
|
||||||
src,
|
|
||||||
path: dirFE+"/"+path.split("/").pop(),
|
|
||||||
exportName,
|
|
||||||
props,
|
|
||||||
});
|
|
||||||
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register client-side resources
|
|
||||||
function registerClientPart() {
|
|
||||||
// Process all component registrations
|
|
||||||
const todo = Array.from(componentsRegistry.entries())
|
|
||||||
.map(([ id, d ]) => {
|
|
||||||
// Copy the component source file to output directory
|
|
||||||
registerClientFile(d.src, {
|
|
||||||
folder: dirFE,
|
|
||||||
// Replace bare imports for browser compatibility
|
|
||||||
replacer(file) {
|
|
||||||
return file.replaceAll(
|
|
||||||
/ from "deka-dom-el(\/signals)?";/g,
|
|
||||||
\` from "./esm-with-signals.js";\`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return [ id, d ];
|
|
||||||
});
|
|
||||||
|
|
||||||
// Serialize the component registry for client-side use
|
|
||||||
const store = JSON.stringify(JSON.stringify(todo));
|
|
||||||
|
|
||||||
// Copy client-side scripts to output
|
|
||||||
registerClientFile(new URL("./ireland.js.js", import.meta.url));
|
|
||||||
registerClientFile(new URL("../../dist/esm-with-signals.js", import.meta.url), { folder: dirFE });
|
|
||||||
|
|
||||||
// Add import map for package resolution
|
|
||||||
document.head.append(
|
|
||||||
el("script", { type: "importmap" }).append(\`
|
|
||||||
{
|
|
||||||
"imports": {
|
|
||||||
"deka-dom-el": "./\${dirFE}/esm-with-signals.js",
|
|
||||||
"deka-dom-el/signals": "./\${dirFE}/esm-with-signals.js"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
\`.trim())
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add bootstrap script to load components
|
|
||||||
document.body.append(
|
|
||||||
el("script", { type: "module" }).append(\`
|
|
||||||
import { loadIrelands } from "./ireland.js.js";
|
|
||||||
loadIrelands(new Map(JSON.parse(\${store})));
|
|
||||||
\`.trim())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
`, page_id }),
|
|
||||||
el("h4", t`Client-Side Hydration`),
|
|
||||||
el(code, { content: `
|
|
||||||
// From docs/components/ireland.js.js - Client-side hydration
|
|
||||||
import { el } from "./irelands/esm-with-signals.js";
|
|
||||||
|
|
||||||
export function loadIrelands(store) {
|
|
||||||
// Find all marked components in the DOM
|
|
||||||
document.body.querySelectorAll("[data-dde-mark]").forEach(ireland => {
|
|
||||||
const { ddeMark } = ireland.dataset;
|
|
||||||
|
|
||||||
// Skip if this component isn't in our registry
|
|
||||||
if(!store.has(ddeMark)) return;
|
|
||||||
|
|
||||||
// Get component information
|
|
||||||
const { path, exportName, props } = store.get(ddeMark);
|
|
||||||
|
|
||||||
// Dynamically import the component module
|
|
||||||
import("./" + path).then(module => {
|
|
||||||
// Replace the server-rendered element with the client-side version
|
|
||||||
ireland.replaceWith(el(module[exportName], props));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
`, page_id }),
|
|
||||||
|
|
||||||
el(h3, t`Live Example`),
|
|
||||||
el("p").append(...T`
|
|
||||||
Here's a live example of an Ireland component showing a standard counter.
|
|
||||||
The component is defined in ${el("code", "docs/components/examples/ireland-test/counter.js")} and
|
|
||||||
rendered with the Ireland component system:
|
|
||||||
`),
|
|
||||||
|
|
||||||
el(code, {
|
|
||||||
src: fileURL("./components/examples/ireland-test/counter.js"),
|
|
||||||
page_id
|
|
||||||
}),
|
|
||||||
el(ireland, {
|
|
||||||
src: fileURL("./components/examples/ireland-test/counter.js"),
|
|
||||||
exportName: "CounterStandard",
|
|
||||||
page_id
|
|
||||||
}),
|
|
||||||
|
|
||||||
el("p").append(...T`
|
|
||||||
When the "Run Component" button is clicked, the component is loaded and rendered dynamically.
|
|
||||||
The counter state is maintained using signals, allowing for reactive updates as you click
|
|
||||||
the buttons to increment and decrement the value.
|
|
||||||
`),
|
|
||||||
|
|
||||||
el(h3, t`Creating Your Own Components`),
|
|
||||||
el("p").append(...T`
|
|
||||||
To create components for use with the Ireland system, follow these guidelines:
|
|
||||||
`),
|
|
||||||
|
|
||||||
el("ol").append(
|
|
||||||
el("li").append(...T`
|
|
||||||
${el("strong", "Export a function:")} Components should be exported as named or default functions
|
|
||||||
`),
|
|
||||||
el("li").append(...T`
|
|
||||||
|
|
||||||
`),
|
|
||||||
el("li").append(...T`
|
|
||||||
${el("strong", "Accept props:")} Components should accept a props object, even if not using it
|
|
||||||
`),
|
|
||||||
el("li").append(...T`
|
|
||||||
${el("strong", "Manage reactivity:")} Use signals for state management where appropriate
|
|
||||||
`),
|
|
||||||
el("li").append(...T`
|
|
||||||
${el("strong", "Handle cleanup:")} Include any necessary cleanup for event listeners or signals
|
|
||||||
`)
|
|
||||||
),
|
|
||||||
|
|
||||||
el(h3, t`Practical Considerations and Limitations`),
|
|
||||||
el("p").append(...T`
|
|
||||||
When implementing Ireland components in real documentation, there are several important
|
|
||||||
considerations to keep in mind:
|
|
||||||
`),
|
|
||||||
|
|
||||||
el("div", { className: "warning" }).append(
|
|
||||||
el("h4", t`Module Resolution and Bundling`),
|
|
||||||
el("p").append(...T`
|
|
||||||
The examples shown here use bare module specifiers like ${el("code", "import { el } from \"deka-dom-el\"")}
|
|
||||||
which aren't supported in all browsers without importmaps. In a production implementation, you would need to:
|
|
||||||
`),
|
|
||||||
el("ol").append(
|
|
||||||
el("li", t`Replace bare import paths with actual paths during the build process`),
|
|
||||||
el("li", t`Bundle component dependencies to avoid multiple requests`),
|
|
||||||
el("li", t`Ensure all module dependencies are properly resolved and copied to the output directory`)
|
|
||||||
),
|
|
||||||
el("p").append(...T`
|
|
||||||
In this documentation, we replace the paths with ${el("code", "./esm-with-signals.js")} and provide
|
|
||||||
a bundled version of the library, but more complex components might require a dedicated bundling step.
|
|
||||||
`)
|
|
||||||
),
|
|
||||||
|
|
||||||
el("div", { className: "note" }).append(
|
|
||||||
el("h4", t`Component Dependencies`),
|
|
||||||
el("p").append(...T`
|
|
||||||
Real-world components typically depend on multiple modules and assets. The Ireland system would need
|
|
||||||
to be extended to:
|
|
||||||
`),
|
|
||||||
el("ul").append(
|
|
||||||
el("li", t`Detect and analyze all dependencies of a component`),
|
|
||||||
el("li", t`Bundle these dependencies together or ensure they're properly copied to the output directory`),
|
|
||||||
el("li", t`Handle non-JavaScript assets like CSS, images, or data files`)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
el(h3, t`Advanced Usage`),
|
|
||||||
el("p").append(...T`
|
|
||||||
The Ireland system can be extended in several ways to address these limitations:
|
|
||||||
`),
|
|
||||||
|
|
||||||
el("ul").append(
|
|
||||||
el("li", t`Integrate with a bundler like esbuild, Rollup, or Webpack`),
|
|
||||||
el("li", t`Add props support for configuring components at runtime`),
|
|
||||||
el("li", t`Implement module caching to reduce network requests`),
|
|
||||||
el("li", t`Add code editing capabilities for interactive experimentation`),
|
|
||||||
el("li", t`Support TypeScript and other languages through transpilation`),
|
|
||||||
el("li", t`Implement state persistence between runs`)
|
|
||||||
),
|
|
||||||
|
|
||||||
el("p").append(...T`
|
|
||||||
This documentation site itself is built using the techniques described here,
|
|
||||||
showcasing how deka-dom-el can be used to create both the documentation and
|
|
||||||
the interactive examples within it. The implementation here is simplified for clarity,
|
|
||||||
while a production-ready system would need to address the considerations above.
|
|
||||||
`)
|
|
||||||
);
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user