mirror of
https://github.com/jaandrle/deka-dom-el
synced 2025-07-01 20:32:13 +02:00
Compare commits
39 Commits
v0.9.4-alp
...
57a5ff2dfe
Author | SHA1 | Date | |
---|---|---|---|
57a5ff2dfe | |||
b3356afa88 | |||
da4e3e52d9 | |||
6c297672c1 | |||
66fdee2c05 | |||
963ed53c84 | |||
59efa84494 | |||
17e40fdd9c | |||
05413cb2bb | |||
5a6f011823 | |||
49243b978a | |||
02f7b3fd67 | |||
7078ec68c1 | |||
41d7728d18 | |||
8f0879196f | |||
9ed6de2f8a | |||
2a3b6dc5cd | |||
1c5f0dab5e | |||
e1f2b32736 | |||
e2df9705d1 | |||
209fa49dee | |||
1b0312f6bd | |||
508d93bb1a | |||
f2ce23d9f7 | |||
b08f75bfb0 | |||
4edc509646 | |||
56232a9f64 | |||
dcf389e28e | |||
bdb20ec298 | |||
7ec50e1660
|
|||
6c4ddd655f
|
|||
198f4a3777
|
|||
3435ea6cfe
|
|||
ed7e6c7963 | |||
3168f452ae
|
|||
b53f3926b3 | |||
8f2fd5a68c | |||
f53b97a89c | |||
f8a94ab9f8
|
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,40 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: ":bug: "
|
|
||||||
labels: bug
|
|
||||||
assignees: ''
|
|
||||||
---
|
|
||||||
|
|
||||||
## Bug Description
|
|
||||||
<!-- A clear and concise description of what the bug is -->
|
|
||||||
|
|
||||||
## Steps to Reproduce
|
|
||||||
<!-- Steps to reproduce the behavior -->
|
|
||||||
1.
|
|
||||||
2.
|
|
||||||
3.
|
|
||||||
|
|
||||||
## Expected Behavior
|
|
||||||
<!-- A clear and concise description of what you expected to happen -->
|
|
||||||
|
|
||||||
## Actual Behavior
|
|
||||||
<!-- A clear and concise description of what actually happened -->
|
|
||||||
|
|
||||||
## Code Sample
|
|
||||||
<!-- If applicable, add minimal code sample to reproduce the issue -->
|
|
||||||
```js
|
|
||||||
// Your code here
|
|
||||||
```
|
|
||||||
|
|
||||||
## Environment
|
|
||||||
- Browser and version: <!-- e.g. Chrome 120, Firefox 120, Safari 17 -->
|
|
||||||
- OS: <!-- e.g. Windows 11, macOS Sonoma, Ubuntu 22.04 -->
|
|
||||||
- dd<el> version: <!-- e.g. 0.9.2 -->
|
|
||||||
- Other relevant details:
|
|
||||||
|
|
||||||
## Screenshots
|
|
||||||
<!-- If applicable, add screenshots to help explain your problem -->
|
|
||||||
|
|
||||||
## Additional Context
|
|
||||||
<!-- Add any other context about the problem here -->
|
|
22
.github/ISSUE_TEMPLATE/documentation.md
vendored
22
.github/ISSUE_TEMPLATE/documentation.md
vendored
@ -1,22 +0,0 @@
|
|||||||
---
|
|
||||||
name: Documentation improvement
|
|
||||||
about: Suggest improvements to the documentation
|
|
||||||
title: ":abc: "
|
|
||||||
labels: documentation
|
|
||||||
assignees: ''
|
|
||||||
---
|
|
||||||
|
|
||||||
## Documentation Area
|
|
||||||
<!-- Which part of the documentation needs improvement? Provide links if applicable -->
|
|
||||||
|
|
||||||
## Current Issue
|
|
||||||
<!-- What's currently unclear, missing, or incorrect in the documentation? -->
|
|
||||||
|
|
||||||
## Suggested Improvement
|
|
||||||
<!-- Describe the improvement or addition you'd like to see -->
|
|
||||||
|
|
||||||
## Example Content
|
|
||||||
<!-- If applicable, provide example content or wording -->
|
|
||||||
|
|
||||||
## Additional Context
|
|
||||||
<!-- Any other context or screenshots about the documentation request -->
|
|
29
.github/ISSUE_TEMPLATE/feature_request.md
vendored
29
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,29 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: ":zap: "
|
|
||||||
labels: enhancement
|
|
||||||
assignees: ''
|
|
||||||
---
|
|
||||||
<!-- Consider open discussion: https://github.com/jaandrle/deka-dom-el/discussions first -->
|
|
||||||
|
|
||||||
## Problem Statement
|
|
||||||
<!-- A clear and concise description of the problem this feature would solve -->
|
|
||||||
|
|
||||||
## Proposed Solution
|
|
||||||
<!-- A detailed description of the feature you're suggesting -->
|
|
||||||
|
|
||||||
## Use Cases
|
|
||||||
<!-- Describe specific use cases where this feature would be beneficial -->
|
|
||||||
|
|
||||||
## Example Implementation
|
|
||||||
<!-- If possible, provide example code or pseudocode for how this feature might work -->
|
|
||||||
```js
|
|
||||||
// Example code
|
|
||||||
```
|
|
||||||
|
|
||||||
## Alternatives Considered
|
|
||||||
<!-- A description of any alternative solutions or features you've considered -->
|
|
||||||
|
|
||||||
## Additional Context
|
|
||||||
<!-- Any other context, screenshots, or examples that might be helpful -->
|
|
39
.github/pull_request_template.md
vendored
39
.github/pull_request_template.md
vendored
@ -1,39 +0,0 @@
|
|||||||
<!--
|
|
||||||
Please use an appropriate git3moji in your PR title: https://robinpokorny.github.io/git3moji/
|
|
||||||
Examples:
|
|
||||||
- :bug: Fix signal update not triggering on nested properties
|
|
||||||
- :zap: Improve event delegation performance
|
|
||||||
- :abc: Add documentation for custom elements
|
|
||||||
-->
|
|
||||||
|
|
||||||
## Description
|
|
||||||
<!-- Describe the changes introduced by this PR -->
|
|
||||||
|
|
||||||
## Related Issues
|
|
||||||
<!-- Link any related issues using the format #ISSUE_NUMBER -->
|
|
||||||
|
|
||||||
## Type of Change
|
|
||||||
- [ ] Bug fix (non-breaking change that fixes an issue)
|
|
||||||
- [ ] New feature (non-breaking change that adds functionality)
|
|
||||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
|
||||||
- [ ] Documentation update
|
|
||||||
- [ ] Code refactoring
|
|
||||||
- [ ] Performance improvement
|
|
||||||
- [ ] Test update
|
|
||||||
|
|
||||||
## Testing Performed
|
|
||||||
<!-- Describe the tests you've done to verify your changes -->
|
|
||||||
|
|
||||||
## Screenshots
|
|
||||||
<!-- If applicable, add screenshots to help explain your changes -->
|
|
||||||
|
|
||||||
## Checklist
|
|
||||||
- [ ] My code follows the code style of this project
|
|
||||||
- [ ] I have performed a self-review of my own code
|
|
||||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
|
||||||
- [ ] I have updated the documentation accordingly
|
|
||||||
- [ ] My changes generate no new warnings
|
|
||||||
- [ ] All existing tests are passing
|
|
||||||
|
|
||||||
## Additional Notes
|
|
||||||
<!-- Any additional information that might be helpful for reviewers -->
|
|
18
.github/workflows/npm-publish.yml
vendored
18
.github/workflows/npm-publish.yml
vendored
@ -1,18 +0,0 @@
|
|||||||
name: Publish Package to npmjs
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
release:
|
|
||||||
types: [created]
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
||||||
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
|
||||||
with:
|
|
||||||
node-version: '20.16'
|
|
||||||
registry-url: 'https://registry.npmjs.org'
|
|
||||||
- run: npm ci
|
|
||||||
- run: npm publish
|
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
3
.npmrc
3
.npmrc
@ -1,3 +0,0 @@
|
|||||||
//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}
|
|
||||||
registry=https://registry.npmjs.org/
|
|
||||||
always-auth=true
|
|
@ -1,134 +0,0 @@
|
|||||||
|
|
||||||
# Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
## Our Pledge
|
|
||||||
|
|
||||||
We as members, contributors, and leaders pledge to make participation in our
|
|
||||||
community a harassment-free experience for everyone, regardless of age, body
|
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
||||||
identity and expression, level of experience, education, socio-economic status,
|
|
||||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
|
||||||
identity and orientation.
|
|
||||||
|
|
||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
||||||
diverse, inclusive, and healthy community.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to a positive environment for our
|
|
||||||
community include:
|
|
||||||
|
|
||||||
* Demonstrating empathy and kindness toward other people
|
|
||||||
* Being respectful of differing opinions, viewpoints, and experiences
|
|
||||||
* Giving and gracefully accepting constructive feedback
|
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
||||||
and learning from the experience
|
|
||||||
* Focusing on what is best not just for us as individuals, but for the overall
|
|
||||||
community
|
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
|
||||||
any kind
|
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
||||||
* Public or private harassment
|
|
||||||
* Publishing others' private information, such as a physical or email address,
|
|
||||||
without their explicit permission
|
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
|
||||||
professional setting
|
|
||||||
|
|
||||||
## Enforcement Responsibilities
|
|
||||||
|
|
||||||
Community leaders are responsible for clarifying and enforcing our standards of
|
|
||||||
acceptable behavior and will take appropriate and fair corrective action in
|
|
||||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
||||||
or harmful.
|
|
||||||
|
|
||||||
Community leaders have the right and responsibility to remove, edit, or reject
|
|
||||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
||||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
||||||
decisions when appropriate.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies within all community spaces, and also applies when
|
|
||||||
an individual is officially representing the community in public spaces.
|
|
||||||
Examples of representing our community include using an official e-mail address,
|
|
||||||
posting via an official social media account, or acting as an appointed
|
|
||||||
representative at an online or offline event.
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
||||||
reported to the community leaders responsible for enforcement at
|
|
||||||
andrle.jan@centrum.cz.
|
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
|
||||||
reporter of any incident.
|
|
||||||
|
|
||||||
## Enforcement Guidelines
|
|
||||||
|
|
||||||
Community leaders will follow these Community Impact Guidelines in determining
|
|
||||||
the consequences for any action they deem in violation of this Code of Conduct:
|
|
||||||
|
|
||||||
### 1. Correction
|
|
||||||
|
|
||||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
||||||
unprofessional or unwelcome in the community.
|
|
||||||
|
|
||||||
**Consequence**: A private, written warning from community leaders, providing
|
|
||||||
clarity around the nature of the violation and an explanation of why the
|
|
||||||
behavior was inappropriate. A public apology may be requested.
|
|
||||||
|
|
||||||
### 2. Warning
|
|
||||||
|
|
||||||
**Community Impact**: A violation through a single incident or series of
|
|
||||||
actions.
|
|
||||||
|
|
||||||
**Consequence**: A warning with consequences for continued behavior. No
|
|
||||||
interaction with the people involved, including unsolicited interaction with
|
|
||||||
those enforcing the Code of Conduct, for a specified period of time. This
|
|
||||||
includes avoiding interactions in community spaces as well as external channels
|
|
||||||
like social media. Violating these terms may lead to a temporary or permanent
|
|
||||||
ban.
|
|
||||||
|
|
||||||
### 3. Temporary Ban
|
|
||||||
|
|
||||||
**Community Impact**: A serious violation of community standards, including
|
|
||||||
sustained inappropriate behavior.
|
|
||||||
|
|
||||||
**Consequence**: A temporary ban from any sort of interaction or public
|
|
||||||
communication with the community for a specified period of time. No public or
|
|
||||||
private interaction with the people involved, including unsolicited interaction
|
|
||||||
with those enforcing the Code of Conduct, is allowed during this period.
|
|
||||||
Violating these terms may lead to a permanent ban.
|
|
||||||
|
|
||||||
### 4. Permanent Ban
|
|
||||||
|
|
||||||
**Community Impact**: Demonstrating a pattern of violation of community
|
|
||||||
standards, including sustained inappropriate behavior, harassment of an
|
|
||||||
individual, or aggression toward or disparagement of classes of individuals.
|
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
|
||||||
community.
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
||||||
version 2.1, available at
|
|
||||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
|
||||||
|
|
||||||
Community Impact Guidelines were inspired by
|
|
||||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see the FAQ at
|
|
||||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
|
||||||
[https://www.contributor-covenant.org/translations][translations].
|
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
|
||||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
|
||||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
|
||||||
[FAQ]: https://www.contributor-covenant.org/faq
|
|
||||||
[translations]: https://www.contributor-covenant.org/translations
|
|
||||||
|
|
177
CONTRIBUTING.md
177
CONTRIBUTING.md
@ -1,177 +0,0 @@
|
|||||||
# Contributing to Deka DOM Elements
|
|
||||||
|
|
||||||
Thank you for your interest in contributing to Deka DOM Elements (dd<el> or DDE)! This document provides guidelines and
|
|
||||||
instructions for contributing to the project.
|
|
||||||
|
|
||||||
## Table of Contents
|
|
||||||
|
|
||||||
- [Code of Conduct](#code-of-conduct)
|
|
||||||
- [Getting Started](#getting-started)
|
|
||||||
- [Development Workflow](#development-workflow)
|
|
||||||
- [Commit Guidelines](#commit-guidelines)
|
|
||||||
- [Pull Request Process](#pull-request-process)
|
|
||||||
- [Issue Guidelines](#issue-guidelines)
|
|
||||||
- [Coding Standards](#coding-standards)
|
|
||||||
- [Testing](#testing)
|
|
||||||
- [Documentation](#documentation)
|
|
||||||
|
|
||||||
## Code of Conduct
|
|
||||||
|
|
||||||
Please be respectful and inclusive in your interactions with other contributors. We aim to foster a welcoming community
|
|
||||||
where everyone feels comfortable participating.
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
1. **Fork the repository**:
|
|
||||||
- Click the "Fork" button on the GitHub repository
|
|
||||||
|
|
||||||
2. **Clone your fork**:
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/YOUR-USERNAME/deka-dom-el.git
|
|
||||||
cd deka-dom-el
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Set up the development environment**:
|
|
||||||
```bash
|
|
||||||
npm ci
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Add the upstream repository**:
|
|
||||||
```bash
|
|
||||||
git remote add upstream https://github.com/jaandrle/deka-dom-el.git
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development Workflow
|
|
||||||
|
|
||||||
1. **Create a new branch**:
|
|
||||||
```bash
|
|
||||||
git checkout -b your-feature-branch
|
|
||||||
```
|
|
||||||
Use descriptive branch names that reflect the changes you're making.
|
|
||||||
|
|
||||||
2. **Make your changes**:
|
|
||||||
- Write clean, modular code
|
|
||||||
- Follow the project's coding standards (see [Coding Standards](#coding-standards))
|
|
||||||
- Include relevant tests for your changes
|
|
||||||
|
|
||||||
3. ~**Run tests**:~
|
|
||||||
```bash
|
|
||||||
#npm test
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Build the project**:
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
#or
|
|
||||||
bs/build.js
|
|
||||||
```
|
|
||||||
|
|
||||||
5. **Preview documentation changes** (if applicable):
|
|
||||||
```bash
|
|
||||||
npm run docs
|
|
||||||
#or
|
|
||||||
bs/docs.js
|
|
||||||
```
|
|
||||||
|
|
||||||
…see [BS folder](./bs/README.md) for more info.
|
|
||||||
|
|
||||||
## 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.
|
|
161
README.md
161
README.md
@ -1,11 +1,17 @@
|
|||||||
**Alpha**
|
**WIP** (the experimentation phase)
|
||||||
| [Docs](https://jaandrle.github.io/deka-dom-el "Official documentation and guide site")
|
| [source code on GitHub](https://github.com/jaandrle/deka-dom-el)
|
||||||
| [NPM](https://www.npmjs.com/package/deka-dom-el "Official NPM package page")
|
| [*mirrored* on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el)
|
||||||
| [GitHub](https://github.com/jaandrle/deka-dom-el "Official GitHub repository")
|
|
||||||
([*Gitea*](https://gitea.jaandrle.cz/jaandrle/deka-dom-el "GitHub repository mirror on my own Gitea instance"))
|
<p align="center">
|
||||||
|
<img src="docs/assets/logo.svg" alt="Deka DOM Elements Logo" width="180" height="180">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
# Deka DOM Elements
|
||||||
|
|
||||||
***Vanilla for flavouring — a full-fledged feast for large projects***
|
***Vanilla for flavouring — a full-fledged feast for large projects***
|
||||||
|
|
||||||
|
*…use simple DOM API by default and library tools and logic when you need them*
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// 🌟 Reactive component with clear separation of concerns
|
// 🌟 Reactive component with clear separation of concerns
|
||||||
document.body.append(
|
document.body.append(
|
||||||
@ -13,41 +19,38 @@ document.body.append(
|
|||||||
);
|
);
|
||||||
|
|
||||||
function EmojiCounter({ initial }) {
|
function EmojiCounter({ initial }) {
|
||||||
// ✨ - Define reactive data
|
// ✨ State - Define reactive data
|
||||||
const count = S(0);
|
const count = S(0);
|
||||||
const emoji = S(initial);
|
const emoji = S(initial);
|
||||||
const textContent = S(() => `Hello World ${emoji.get().repeat(count.get())}`);
|
|
||||||
|
|
||||||
// 🔄 - UI updates automatically when signals change
|
/** @param {HTMLOptionElement} el */
|
||||||
|
const isSelected= el=> (el.selected= el.value===initial);
|
||||||
|
|
||||||
|
// 🔄 View - UI updates automatically when signals change
|
||||||
return el().append(
|
return el().append(
|
||||||
el("p", { textContent, className: "output" }),
|
el("p", {
|
||||||
|
className: "output",
|
||||||
|
textContent: S(() =>
|
||||||
|
`Hello World ${emoji.get().repeat(clicks.get())}`),
|
||||||
|
}),
|
||||||
|
|
||||||
// 🎮 - Update state on events
|
// 🎮 Controls - Update state on events
|
||||||
el("button", { textContent: "Add Emoji" },
|
el("button", { textContent: "Add Emoji" },
|
||||||
on("click", () => count.set(count.get() + 1)),
|
on("click", () => count.set(count.get() + 1))
|
||||||
),
|
),
|
||||||
|
|
||||||
el("select", null,
|
el("select", null, on("change", e => emoji.set(e.target.value)))
|
||||||
on.defer(el=> el.value= initial),
|
.append(
|
||||||
on("change", e => emoji.set(e.target.value)),
|
el(Option, "🎉", isSelected),
|
||||||
).append(
|
el(Option, "🚀", isSelected),
|
||||||
el(Option, "🎉"),
|
el(Option, "💖", isSelected),
|
||||||
el(Option, "🚀"),
|
|
||||||
el(Option, "💖"),
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
function Option({ textContent }){
|
function Option({ textContent }){
|
||||||
return el("option", { value: textContent, textContent });
|
return Ol("option", { value: textContent, textContent });
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
*…use simple DOM API by default and library tools and logic when you need them*
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<img src="docs/assets/logo.svg" alt="Deka DOM Elements Logo" width="180" height="180">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
# Deka DOM Elements (dd\<el\> or DDE)
|
|
||||||
|
|
||||||
Creating reactive elements, components, and Web Components using the native
|
Creating reactive elements, components, and Web Components using the native
|
||||||
[IDL](https://developer.mozilla.org/en-US/docs/Glossary/IDL)/JavaScript DOM API enhanced with
|
[IDL](https://developer.mozilla.org/en-US/docs/Glossary/IDL)/JavaScript DOM API enhanced with
|
||||||
@ -56,63 +59,43 @@ Creating reactive elements, components, and Web Components using the native
|
|||||||
## Features at a Glance
|
## Features at a Glance
|
||||||
|
|
||||||
- ✅ **No build step required** — use directly in browsers or Node.js
|
- ✅ **No build step required** — use directly in browsers or Node.js
|
||||||
- ☑️ **Lightweight** — ~10-15kB minified (original goal 10kB) with **zero**/minimal dependencies
|
- ☑️ **Lightweight** — ~10-15kB minified (original goal 10kB) with zero/minimal dependencies
|
||||||
- ✅ **Declarative & functional approach** for clean, maintainable code
|
- ✅ **Declarative & functional approach** for clean, maintainable code
|
||||||
- ✅ **Signals and events** for reactive UI
|
- ✅ **Optional signals** with support for custom reactive implementations
|
||||||
- ✅ **Auto-releasing resources** for memory management but nice development experience
|
- ✅ **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom)
|
||||||
- ✅ **Memoization for performance** — optimize rendering with intelligent caching
|
- 🔄 **TypeScript support** (work in progress)
|
||||||
- ☑️ **Optional build-in signals** with support for custom reactive implementations (#39)
|
- 🔄 **Enhanced Web Components** support (work in progress)
|
||||||
- ☑️ **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, dd\<el\> starts with pure JavaScript (DOM API) and gradually adds
|
Following functional programming principles, Deka DOM Elements starts with pure JavaScript (DOM API) and gradually adds auxiliary functions. These range from minor improvements to advanced features for building complete declarative reactive UI templates.
|
||||||
auxiliary functions. These range from minor improvements to advanced features for building complete declarative
|
|
||||||
reactive UI templates.
|
|
||||||
|
|
||||||
A key advantage: any internal function (`assign`, `classListDeclarative`, `on`, `dispatchEvent`, `S`, etc.) can be used
|
A key advantage: any internal function (`assign`, `classListDeclarative`, `on`, `dispatchEvent`, `S`, etc.) can be used independently while also working seamlessly together. This modular approach makes it easier to integrate the library into existing projects.
|
||||||
independently while also working seamlessly together. This modular approach makes it easier to integrate the library
|
|
||||||
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
|
||||||
|
|
||||||
@ -123,24 +106,10 @@ Signals are the reactive backbone of Deka DOM Elements:
|
|||||||
- [TC39 Signals Proposal](https://github.com/tc39/proposal-signals) (future standard)
|
- [TC39 Signals Proposal](https://github.com/tc39/proposal-signals) (future standard)
|
||||||
- [Observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) (underlying concept)
|
- [Observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) (underlying concept)
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
We welcome contributions from the community! Please see our [Contributing Guide](CONTRIBUTING.md) for details on how to
|
|
||||||
get started, coding standards, commit guidelines, and the pull request process.
|
|
||||||
|
|
||||||
## Inspiration and Alternatives
|
## Inspiration and Alternatives
|
||||||
|
|
||||||
- [vanjs-org/van](https://github.com/vanjs-org/van) — World's smallest reactive UI framework
|
- [vanjs-org/van](https://github.com/vanjs-org/van) - World's smallest reactive UI framework
|
||||||
- [adamhaile/S](https://github.com/adamhaile/S) — Simple, clean, fast reactive programming
|
- [adamhaile/S](https://github.com/adamhaile/S) - Simple, clean, fast reactive programming
|
||||||
- [hyperhype/hyperscript](https://github.com/hyperhype/hyperscript) — Create HyperText with JavaScript
|
- [hyperhype/hyperscript](https://github.com/hyperhype/hyperscript) - Create HyperText with JavaScript
|
||||||
- [potch/signals](https://github.com/potch/signals) — A small reactive signals library
|
- [potch/signals](https://github.com/potch/signals) - A small reactive signals library
|
||||||
- [AseasRoa/paintor](https://github.com/AseasRoa/paintor) - JavaScript library for building reactive client-side user
|
- [jaandrle/dollar_dom_component](https://github.com/jaandrle/dollar_dom_component) - 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,18 +2,15 @@
|
|||||||
This project uses [jaandrle/bs: The simplest possible build system using executable/bash scripts](
|
This project uses [jaandrle/bs: The simplest possible build system using executable/bash scripts](
|
||||||
https://github.com/jaandrle/bs).
|
https://github.com/jaandrle/bs).
|
||||||
|
|
||||||
#### bs/build.js [main|signals] [--no-types|--help]
|
#### bs/build.js [--minify|--help]
|
||||||
Generates alternative versions of the project (other than native ESM code).
|
Generates alternative versions of the project (other than native ESM code).
|
||||||
Also generates typescript definitions.
|
Also generates typescript definitions.
|
||||||
|
|
||||||
#### bs/docs.js
|
#### bs/docs.js
|
||||||
Generates documentation, from `docs/`. Uses “SSR” technique, using deka-dom-el itself.
|
Generates documentation, from `docs/`. Uses “SSR” technique, using deka-dom-el itself.
|
||||||
|
|
||||||
For running use `npx serve dist/docs`.
|
|
||||||
|
|
||||||
#### bs/lint.sh
|
#### bs/lint.sh
|
||||||
Lints size of the project, jshint. See configs:
|
Lints size of the project, jshint. See configs:
|
||||||
|
|
||||||
- `package.json`: key `size-limit`
|
- `package.json`: key `size-limit`
|
||||||
- `package.json`: key `jshintConfig`
|
- `package.json`: key `jshintConfig`
|
||||||
- `.editorconfig`
|
|
||||||
|
17
bs/build.js
17
bs/build.js
@ -4,33 +4,30 @@ const files= [ "index", "index-with-signals" ];
|
|||||||
|
|
||||||
$.api("")
|
$.api("")
|
||||||
.command("main", "Build main files", { default: true })
|
.command("main", "Build main files", { default: true })
|
||||||
.option("--no-types", "Also generate d.ts files", false)
|
.action(async function main(){
|
||||||
.action(function main({ types }){
|
const regular = await build({
|
||||||
const regular = build({
|
|
||||||
files,
|
files,
|
||||||
filesOut,
|
filesOut,
|
||||||
minify: "no",
|
minify: "no",
|
||||||
types,
|
|
||||||
});
|
});
|
||||||
const min = build({
|
const min = await build({
|
||||||
files,
|
files,
|
||||||
filesOut(file, mark= "esm"){
|
filesOut(file, mark= "esm"){
|
||||||
const out= filesOut(file, mark);
|
const out= filesOut(file, mark);
|
||||||
const idx= out.indexOf(".");
|
const idx= out.lastIndexOf(".");
|
||||||
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(function signals(){
|
.action(async function signals(){
|
||||||
const regular = build({
|
const regular = await build({
|
||||||
files: [ "signals" ],
|
files: [ "signals" ],
|
||||||
filesOut(file){ return "dist/."+file; },
|
filesOut(file){ return "dist/."+file; },
|
||||||
minify: "no",
|
minify: "no",
|
||||||
iife: false,
|
dde: false,
|
||||||
});
|
});
|
||||||
return $.exit(regular);
|
return $.exit(regular);
|
||||||
})
|
})
|
||||||
|
128
bs/dev/.build.js
128
bs/dev/.build.js
@ -1,101 +1,65 @@
|
|||||||
#!/usr/bin/env -S npx nodejsscript
|
#!/usr/bin/env -S npx nodejsscript
|
||||||
import { buildSync as esbuildSync } from "esbuild";
|
import { bundle as bundleDTS } from "dts-bundler";
|
||||||
const css= echo.css`
|
const css= echo.css`
|
||||||
.info{ color: gray; }
|
.info{ color: gray; }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function build({ files, filesOut, minify= "partial", iife= true, types= true }){
|
export async function build({ files, filesOut, minify= "partiala", dde= 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);
|
||||||
esbuild({ file, out, minify });
|
const esbuild_output= s.$().run([
|
||||||
|
"npx esbuild '::file::'",
|
||||||
if(types){
|
"--platform=neutral",
|
||||||
const file_dts= file_root+".d.ts";
|
"--bundle",
|
||||||
const file_dts_out= filesOut(file_dts);
|
minifyOption(minify),
|
||||||
echoVariant(file_dts_out, true);
|
"--legal-comments=inline",
|
||||||
buildDts({
|
"--packages=external",
|
||||||
bundle: out,
|
"--outfile='::out::'"
|
||||||
entry: file_dts,
|
].filter(Boolean).join(" "), { file, out });
|
||||||
});
|
if(esbuild_output.code)
|
||||||
echoVariant(file_dts_out);
|
return $.exit(esbuild_output.code, echo(esbuild_output.stderr));
|
||||||
}
|
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));
|
||||||
|
|
||||||
echoVariant(metaToLineStatus(esbuild_output.metafile, out));
|
const file_dts= file_root+".d.ts";
|
||||||
return esbuild_output;
|
const file_dts_out= filesOut(file_dts);
|
||||||
|
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 { minify: false };
|
if("no"===level) return undefined;
|
||||||
if("full"===level) return { minify: true };
|
if("full"===level) return "--minify";
|
||||||
return { minifySyntax: true, minifyIdentifiers: true };
|
return "--minify-syntax --minify-identifiers";
|
||||||
}
|
}
|
||||||
function metaToLineStatus(meta, file){
|
function echoVariant(name){
|
||||||
const status= meta.outputs[file];
|
return echo("%c✓ "+name, css.info+css);
|
||||||
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, currentPageId, dispatchEvent, t } from "../docs/ssr.js";
|
import { path_target, pages as pages_registered, styles, dispatchEvent, t } from "../docs/ssr.js";
|
||||||
import { createHTMl } from "./docs/jsdom.js";
|
import { createHTMl } from "./docs/jsdom.js";
|
||||||
import { register, queue } from "../jsdom.js";
|
import { register, queue } from "../jsdom.js";
|
||||||
const pkg= s.cat("package.json").xargs(JSON.parse);
|
const pkg= s.cat("package.json").xargs(JSON.parse);
|
||||||
@ -28,7 +28,6 @@ for(const { id, info } of pages){
|
|||||||
);
|
);
|
||||||
const { el }= await register(serverDOM.dom);
|
const { el }= await register(serverDOM.dom);
|
||||||
const { page }= await import(`../docs/${id}.html.js`);
|
const { page }= await import(`../docs/${id}.html.js`);
|
||||||
currentPageId(id)
|
|
||||||
serverDOM.document.body.append(
|
serverDOM.document.body.append(
|
||||||
el(page, { pkg, info }),
|
el(page, { pkg, info }),
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { JSDOM } from "jsdom";
|
import { JSDOM } from "jsdom";
|
||||||
const html_default= "<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"></head><body></body></html>";
|
const html_default= "<!doctype html><html><head><meta charset=\"utf-8\"></head><body></body></html>";
|
||||||
let keys= [];
|
let keys= [];
|
||||||
let dom= null;
|
let dom= null;
|
||||||
import { relative } from 'node:path';
|
import { relative } from 'node:path';
|
||||||
|
10
bs/lint.sh
10
bs/lint.sh
@ -1,11 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -eou pipefail
|
set -eou pipefail
|
||||||
# if $1=vim -no-color
|
npx editorconfig-checker -format gcc
|
||||||
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
|
||||||
|
1012
dist/dde-with-signals.js
vendored
Normal file
1012
dist/dde-with-signals.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
31
dist/dde-with-signals.min.js
vendored
Normal file
31
dist/dde-with-signals.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
667
dist/dde.js
vendored
Normal file
667
dist/dde.js
vendored
Normal file
@ -0,0 +1,667 @@
|
|||||||
|
//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
Normal file
25
dist/dde.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
606
dist/esm-with-signals.d.min.ts
vendored
Normal file
606
dist/esm-with-signals.d.min.ts
vendored
Normal file
@ -0,0 +1,606 @@
|
|||||||
|
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 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>
|
||||||
|
|
||||||
|
/* 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>
|
||||||
|
}
|
1060
dist/esm-with-signals.d.ts
vendored
1060
dist/esm-with-signals.d.ts
vendored
File diff suppressed because it is too large
Load Diff
871
dist/esm-with-signals.js
vendored
871
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
860
dist/esm-with-signals.min.d.ts
vendored
@ -1,860 +0,0 @@
|
|||||||
// 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
533
dist/esm.d.min.ts
vendored
Normal file
533
dist/esm.d.min.ts
vendored
Normal file
@ -0,0 +1,533 @@
|
|||||||
|
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 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>
|
||||||
|
|
||||||
|
/* 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
|
988
dist/esm.d.ts
vendored
988
dist/esm.d.ts
vendored
File diff suppressed because it is too large
Load Diff
800
dist/esm.js
vendored
800
dist/esm.js
vendored
@ -1,5 +1,4 @@
|
|||||||
// 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";
|
||||||
}
|
}
|
||||||
@ -9,9 +8,6 @@ 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);
|
||||||
}
|
}
|
||||||
@ -25,18 +21,53 @@ function onAbort(signal, listener) {
|
|||||||
signal.removeEventListener("abort", listener);
|
signal.removeEventListener("abort", listener);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function requestIdle() {
|
function observedAttributes(instance, observedAttribute) {
|
||||||
return new Promise(function(resolve) {
|
const { observedAttributes: observedAttributes3 = [] } = instance.constructor;
|
||||||
(globalThis.requestIdleCallback || requestAnimationFrame)(resolve);
|
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/dom-lib/common.js
|
// src/signals-lib/common.js
|
||||||
|
var signals_global = {
|
||||||
|
/**
|
||||||
|
* Checks if a value is a signal
|
||||||
|
* @param {any} attributes - Value to check
|
||||||
|
* @returns {boolean} Whether the value is a signal
|
||||||
|
*/
|
||||||
|
isSignal(attributes) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Processes an attribute that might be reactive
|
||||||
|
* @param {Element} obj - Element that owns the attribute
|
||||||
|
* @param {string} key - Attribute name
|
||||||
|
* @param {any} attr - Attribute value
|
||||||
|
* @param {Function} set - Function to set the attribute
|
||||||
|
* @returns {any} Processed attribute value
|
||||||
|
*/
|
||||||
|
processReactiveAttribute(obj, key, attr, set) {
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function registerReactivity(def, global = true) {
|
||||||
|
if (global) return oAssign(signals_global, def);
|
||||||
|
Object.setPrototypeOf(def, signals_global);
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
function signals(_this) {
|
||||||
|
return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global;
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/dom-common.js
|
||||||
var enviroment = {
|
var enviroment = {
|
||||||
setDeleteAttr,
|
setDeleteAttr,
|
||||||
ssr: "",
|
ssr: "",
|
||||||
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,
|
||||||
@ -57,7 +88,276 @@ var evc = "dde:connected";
|
|||||||
var evd = "dde:disconnected";
|
var evd = "dde:disconnected";
|
||||||
var eva = "dde:attributeChanged";
|
var eva = "dde:attributeChanged";
|
||||||
|
|
||||||
// src/dom-lib/events-observer.js
|
// src/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({}, {
|
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
|
||||||
get() {
|
get() {
|
||||||
return () => {
|
return () => {
|
||||||
@ -168,13 +468,18 @@ 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, enviroment.N)) return out;
|
if (!isInstance(element, Node)) return out;
|
||||||
for (const el of store.keys()) {
|
for (const el of store.keys()) {
|
||||||
if (el === element || !isInstance(el, enviroment.N)) continue;
|
if (el === element || !isInstance(el, Node)) continue;
|
||||||
if (element.contains(el))
|
if (element.contains(el))
|
||||||
out.push(el);
|
out.push(el);
|
||||||
}
|
}
|
||||||
@ -216,371 +521,8 @@ function connectionsChangesObserverConstructor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/dom-lib/events.js
|
// src/customElement.js
|
||||||
function dispatchEvent(name, options, host) {
|
function customElementRender(target, render, props = observedAttributes2) {
|
||||||
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 = 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;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
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,
|
||||||
@ -621,40 +563,81 @@ 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/memo.js
|
// src/events.js
|
||||||
var memoMark = "__dde_memo";
|
function dispatchEvent(name, options, host) {
|
||||||
var memo_scope = [];
|
if (typeof options === "function") {
|
||||||
function memo(key, generator) {
|
host = options;
|
||||||
if (!memo_scope.length) return generator(key);
|
options = null;
|
||||||
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) {
|
if (!options) options = {};
|
||||||
return obj[memoMark];
|
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);
|
||||||
};
|
};
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
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 out = fun.apply(this, args);
|
const c = onAbort(options.signal, () => observer.disconnect());
|
||||||
memo_scope.shift();
|
if (c) observer.observe(element, { attributes: true });
|
||||||
cache = cache_local;
|
return element;
|
||||||
return out;
|
};
|
||||||
}
|
|
||||||
memoScope[memoMark] = true;
|
|
||||||
memoScope.clear = () => cache = oCreate();
|
|
||||||
if (signal) signal.addEventListener("abort", memoScope.clear);
|
|
||||||
return memoScope;
|
|
||||||
};
|
};
|
||||||
export {
|
export {
|
||||||
assign,
|
assign,
|
||||||
@ -668,8 +651,9 @@ export {
|
|||||||
dispatchEvent,
|
dispatchEvent,
|
||||||
createElement as el,
|
createElement as el,
|
||||||
createElementNS as elNS,
|
createElementNS as elNS,
|
||||||
|
elementAttribute,
|
||||||
lifecyclesToEvents,
|
lifecyclesToEvents,
|
||||||
memo,
|
observedAttributes2 as observedAttributes,
|
||||||
on,
|
on,
|
||||||
queue,
|
queue,
|
||||||
registerReactivity,
|
registerReactivity,
|
||||||
|
859
dist/esm.min.d.ts
vendored
859
dist/esm.min.d.ts
vendored
@ -1,859 +0,0 @@
|
|||||||
// 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
862
dist/iife-with-signals.d.ts
vendored
@ -1,862 +0,0 @@
|
|||||||
// 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 {};
|
|
1028
dist/iife-with-signals.js
vendored
1028
dist/iife-with-signals.js
vendored
File diff suppressed because it is too large
Load Diff
862
dist/iife-with-signals.min.d.ts
vendored
862
dist/iife-with-signals.min.d.ts
vendored
@ -1,862 +0,0 @@
|
|||||||
// 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
3
dist/iife-with-signals.min.js
vendored
File diff suppressed because one or more lines are too long
861
dist/iife.d.ts
vendored
861
dist/iife.d.ts
vendored
@ -1,861 +0,0 @@
|
|||||||
// 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 {};
|
|
702
dist/iife.js
vendored
702
dist/iife.js
vendored
@ -1,702 +0,0 @@
|
|||||||
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 = 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;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
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
861
dist/iife.min.d.ts
vendored
@ -1,861 +0,0 @@
|
|||||||
// 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
1
dist/iife.min.js
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
Before Width: | Height: | Size: 145 KiB |
@ -1,60 +1,28 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<svg
|
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||||
width="64"
|
<defs>
|
||||||
height="64"
|
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
viewBox="0 0 64 64"
|
<stop offset="0%" stop-color="#333333" />
|
||||||
version="1.1"
|
<stop offset="100%" stop-color="#222222" />
|
||||||
id="svg2"
|
</linearGradient>
|
||||||
sodipodi:docname="favicon.svg"
|
<linearGradient id="textGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
xml:space="preserve"
|
<stop offset="0%" stop-color="#e32c2c" />
|
||||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
<stop offset="100%" stop-color="#ff5252" />
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
</linearGradient>
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
<filter id="shadow" x="-10%" y="-10%" width="120%" height="120%">
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<feDropShadow dx="0" dy="0.5" stdDeviation="0.5" flood-color="#000" flood-opacity="0.3"/>
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
</filter>
|
||||||
id="defs2" /><sodipodi:namedview
|
</defs>
|
||||||
id="namedview2"
|
|
||||||
pagecolor="#ffffff"
|
<!-- Square background with rounded corners -->
|
||||||
bordercolor="#000000"
|
<rect x="2" y="2" width="28" height="28" rx="4" ry="4" fill="url(#bgGradient)" />
|
||||||
borderopacity="0.25"
|
|
||||||
inkscape:showpageshadow="2"
|
<!-- Subtle code brackets as background element -->
|
||||||
inkscape:pageopacity="0.0"
|
<g opacity="0.15" fill="#fff">
|
||||||
inkscape:pagecheckerboard="0"
|
<path d="M10,7.5 L6.25,16 L10,24.5" stroke="#fff" stroke-width="1" fill="none"/>
|
||||||
inkscape:deskcolor="#d1d1d1"
|
<path d="M22,7.5 L25.75,16 L22,24.5" stroke="#fff" stroke-width="1" fill="none"/>
|
||||||
inkscape:zoom="9.3249707"
|
</g>
|
||||||
inkscape:cx="38.230683"
|
|
||||||
inkscape:cy="28.150223"
|
<!-- lowercase dde letters -->
|
||||||
inkscape:window-width="1278"
|
<text x="16" y="21" text-anchor="middle" font-family="'Fira Code', 'JetBrains Mono', 'Source Code Pro', 'SF Mono', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace" font-size="14" font-weight="bold" fill="url(#textGradient)" filter="url(#shadow)">dde</text>
|
||||||
inkscape:window-height="1023"
|
</svg>
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="0"
|
|
||||||
inkscape:window-maximized="0"
|
|
||||||
inkscape:current-layer="svg2" /><rect
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
width="64"
|
|
||||||
height="64"
|
|
||||||
rx="10"
|
|
||||||
fill="#2b2b2b"
|
|
||||||
id="rect1" /><g
|
|
||||||
transform="matrix(0.98009485,0,0,1.1964871,0.58334513,-7.2134367)"
|
|
||||||
id="g2"
|
|
||||||
style="stroke:#666666"><g
|
|
||||||
id="g3"
|
|
||||||
transform="matrix(1.0027126,0,0,0.88864615,3.8540039,4.3371297)"
|
|
||||||
style="stroke:#666666"><path
|
|
||||||
d="M 8.880742,11.550781 2.9817709,32 8.880742,52.449219"
|
|
||||||
stroke="#ff5252"
|
|
||||||
stroke-width="4.25375"
|
|
||||||
fill="none"
|
|
||||||
id="path1"
|
|
||||||
style="stroke:#666666" /><path
|
|
||||||
d="M 47.330923,11.568743 53.261141,32 47.330923,52.431257"
|
|
||||||
stroke="#ff5252"
|
|
||||||
stroke-width="4.26309"
|
|
||||||
fill="none"
|
|
||||||
id="path2"
|
|
||||||
style="stroke:#666666" /></g></g><path
|
|
||||||
d="m 11.726038,32.656946 q 0,-2.685867 0.823088,-4.721927 0.823088,-2.036061 2.166022,-3.404987 1.342933,-1.360261 3.07575,-2.053387 1.732818,-0.693128 3.552275,-0.693128 4.505325,0 6.844629,2.659875 2.339303,2.668539 2.339303,7.780349 0,0.519846 -0.01733,1.083011 -0.02599,0.563166 -0.06931,0.90973 H 17.227733 q 0,1.992739 1.646176,3.145062 1.646176,1.14366 4.245402,1.14366 1.602856,0 3.058423,-0.346564 1.446902,-0.346563 2.443272,-0.693127 l 0.736447,4.548646 q -1.386253,0.476525 -2.945789,0.797097 -1.559536,0.329235 -3.508954,0.329235 -2.599226,0 -4.652615,-0.675799 -2.062053,-0.667135 -3.508955,-1.984075 -1.455566,-1.325605 -2.235335,-3.275025 -0.779767,-1.94942 -0.779767,-4.548646 m 13.645936,-2.1227 q 0,-0.823088 -0.216602,-1.585528 -0.216602,-0.753776 -0.693127,-1.360263 -0.476525,-0.606485 -1.212973,-0.979042 -0.736447,-0.363891 -1.819457,-0.363891 -1.039691,0 -1.793467,0.346563 -0.762439,0.346564 -1.264955,0.95305 -0.493854,0.606485 -0.771105,1.386253 -0.285915,0.77977 -0.372555,1.602856 z m 26.901988,11.263311 q -0.129961,0.08664 -0.589158,0.303244 -0.450533,0.216602 -1.18698,0.459196 -0.736448,0.23393 -1.793466,0.407211 -1.065682,0.173282 -2.408616,0.173282 -3.682236,0 -5.371734,-2.192014 -1.689496,-2.18335 -1.689496,-6.385432 V 17.278193 H 33.602856 V 12.85951 h 10.960069 v 22.093419 q 0,2.079381 0.823089,2.815829 0.823088,0.736447 2.07938,0.736447 1.602856,0 2.685867,-0.433204 1.08301,-0.433205 1.429574,-0.563166 z"
|
|
||||||
id="path1-1"
|
|
||||||
style="fill:#ff5252;stroke-width:0.866409" /></svg>
|
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.4 KiB |
@ -1,92 +1,33 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<svg
|
<svg width="256" height="256" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||||
width="256"
|
<!-- Gradients and effects -->
|
||||||
height="256"
|
<defs>
|
||||||
viewBox="0 0 256 256"
|
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
version="1.1"
|
<stop offset="0%" stop-color="#333333" />
|
||||||
id="svg5"
|
<stop offset="100%" stop-color="#222222" />
|
||||||
xml:space="preserve"
|
</linearGradient>
|
||||||
sodipodi:docname="logo.svg"
|
<linearGradient id="textGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
<stop offset="0%" stop-color="#e32c2c" />
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
<stop offset="100%" stop-color="#ff5252" />
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
</linearGradient>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<filter id="shadow" x="-10%" y="-10%" width="120%" height="120%">
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
<feDropShadow dx="0" dy="2" stdDeviation="2" flood-color="#000" flood-opacity="0.3"/>
|
||||||
id="namedview1"
|
</filter>
|
||||||
pagecolor="#ffffff"
|
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
bordercolor="#000000"
|
<feGaussianBlur stdDeviation="4" result="blur"/>
|
||||||
borderopacity="0.25"
|
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
|
||||||
inkscape:showpageshadow="2"
|
</filter>
|
||||||
inkscape:pageopacity="0.0"
|
</defs>
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
inkscape:deskcolor="#d1d1d1"
|
<!-- Square background with rounded corners -->
|
||||||
inkscape:zoom="3.296875"
|
<rect x="18" y="18" width="220" height="220" rx="20" ry="20" fill="url(#bgGradient)" />
|
||||||
inkscape:cx="128"
|
|
||||||
inkscape:cy="101.61137"
|
<!-- Subtle code brackets as background element -->
|
||||||
inkscape:window-width="1920"
|
<g opacity="0.15" fill="#fff" filter="url(#glow)">
|
||||||
inkscape:window-height="1052"
|
<path d="M80,60 L50,128 L80,196" stroke="#fff" stroke-width="8" fill="none"/>
|
||||||
inkscape:window-x="0"
|
<path d="M176,60 L206,128 L176,196" stroke="#fff" stroke-width="8" fill="none"/>
|
||||||
inkscape:window-y="0"
|
</g>
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:current-layer="g2" /><defs
|
<!-- lowercase dde letters with shadow effect -->
|
||||||
id="defs4"><linearGradient
|
<text x="128" y="154" text-anchor="middle" font-family="'Fira Code', 'JetBrains Mono', 'Source Code Pro', 'SF Mono', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace" font-size="100" font-weight="bold" fill="url(#textGradient)" filter="url(#shadow)">dde</text>
|
||||||
id="bgGradient"
|
</svg>
|
||||||
x1="18"
|
|
||||||
y1="18"
|
|
||||||
x2="238"
|
|
||||||
y2="238"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
gradientTransform="matrix(1.1363636,0,0,1.1363636,-17.454544,-17.454544)"><stop
|
|
||||||
offset="0"
|
|
||||||
stop-color="#3b3b3b"
|
|
||||||
id="stop1" /><stop
|
|
||||||
offset="1"
|
|
||||||
stop-color="#2b2b2b"
|
|
||||||
id="stop2" /></linearGradient><filter
|
|
||||||
id="glow"
|
|
||||||
x="-0.089563958"
|
|
||||||
y="-0.082460006"
|
|
||||||
width="1.1791279"
|
|
||||||
height="1.16492"><feGaussianBlur
|
|
||||||
stdDeviation="4"
|
|
||||||
result="blur"
|
|
||||||
id="feGaussianBlur4"
|
|
||||||
in="SourceGraphic" /><feComposite
|
|
||||||
in="SourceGraphic"
|
|
||||||
in2="blur"
|
|
||||||
operator="over"
|
|
||||||
id="feComposite4"
|
|
||||||
result="composite-0" /></filter></defs><rect
|
|
||||||
x="3"
|
|
||||||
y="3"
|
|
||||||
width="250"
|
|
||||||
height="250"
|
|
||||||
fill="url(#bgGradient)"
|
|
||||||
id="rect4"
|
|
||||||
style="fill:url(#bgGradient);stroke-width:1.13636"
|
|
||||||
ry="50" /><g
|
|
||||||
id="g2"
|
|
||||||
transform="translate(0.4430186,-1.5165883)"><g
|
|
||||||
id="g1"
|
|
||||||
transform="matrix(1.5900346,0,0,1.5900346,-121.12651,-66.626074)"><g
|
|
||||||
opacity="0.25"
|
|
||||||
fill="#ffffff"
|
|
||||||
filter="url(#glow)"
|
|
||||||
id="g5"
|
|
||||||
transform="matrix(0.55415879,0,0,0.56134669,112.16444,51.505106)"><path
|
|
||||||
d="m 80,60 -30,68 30,68"
|
|
||||||
stroke="#ffffff"
|
|
||||||
stroke-width="8"
|
|
||||||
fill="none"
|
|
||||||
id="path4" /><path
|
|
||||||
d="m 176,60 30,68 -30,68"
|
|
||||||
stroke="#ffffff"
|
|
||||||
stroke-width="8"
|
|
||||||
fill="none"
|
|
||||||
id="path5" /></g><path
|
|
||||||
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"
|
|
||||||
style="fill:#ff5252;stroke-width:1.31243" /></g><path
|
|
||||||
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"
|
|
||||||
style="fill:#ff5252;fill-opacity:0.664797;stroke-width:2.1097" /></g></svg>
|
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 1.7 KiB |
@ -1,4 +1,4 @@
|
|||||||
import { page_id, registerClientFile, styles } from "../ssr.js";
|
import { registerClientFile, styles } from "../ssr.js";
|
||||||
const host= "."+code.name;
|
const host= "."+code.name;
|
||||||
styles.css`
|
styles.css`
|
||||||
/* Code block styling */
|
/* Code block styling */
|
||||||
@ -177,9 +177,6 @@ ${host}:hover .copy-button {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
import { el } from "deka-dom-el";
|
import { el } from "deka-dom-el";
|
||||||
/**
|
|
||||||
* @typedef {"js"|"ts"|"html"|"css"|"shell"|"-"} Language
|
|
||||||
* */
|
|
||||||
/**
|
/**
|
||||||
* Prints code to the page and registers flems to make it interactive.
|
* Prints code to the page and registers flems to make it interactive.
|
||||||
* @param {object} attrs
|
* @param {object} attrs
|
||||||
@ -187,35 +184,56 @@ import { el } from "deka-dom-el";
|
|||||||
* @param {string} [attrs.className]
|
* @param {string} [attrs.className]
|
||||||
* @param {URL} [attrs.src] Example code file path
|
* @param {URL} [attrs.src] Example code file path
|
||||||
* @param {string} [attrs.content] Example code
|
* @param {string} [attrs.content] Example code
|
||||||
* @param {Language} [attrs.language="-s"] Language of the code
|
* @param {"js"|"ts"|"html"|"css"} [attrs.language="js"] Language of the code
|
||||||
|
* @param {string} [attrs.page_id] ID of the page, if setted it registers shiki
|
||||||
* */
|
* */
|
||||||
export function code({ id, src, content, language= "-", className= host.slice(1) }){
|
export function code({ id, src, content, language= "js", className= host.slice(1), page_id }){
|
||||||
if(src){
|
if(src) content= s.cat(src);
|
||||||
content= s.cat(src);
|
|
||||||
if(language=== "-") language= /** @type {Language} */(src.pathname.split(".").pop());
|
|
||||||
}
|
|
||||||
content= normalizeIndentation(content);
|
|
||||||
let dataJS;
|
let dataJS;
|
||||||
if(language!== "-"){
|
if(page_id){
|
||||||
registerClientPart();
|
registerClientPart(page_id);
|
||||||
dataJS= "todo";
|
dataJS= "todo";
|
||||||
}
|
}
|
||||||
return el("div", { id, className, dataJS, tabIndex: 0 }).append(
|
return el("div", { id, className, dataJS }).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= {};
|
||||||
function registerClientPart(){
|
/** @param {string} page_id */
|
||||||
|
function registerClientPart(page_id){
|
||||||
if(is_registered[page_id]) return;
|
if(is_registered[page_id]) return;
|
||||||
|
|
||||||
// Add Shiki with a more reliable loading method
|
// Add Shiki with a more reliable loading method
|
||||||
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(
|
||||||
@ -227,9 +245,3 @@ function registerClientPart(){
|
|||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
@ -1,175 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
@ -1,384 +0,0 @@
|
|||||||
import { el, on } from "deka-dom-el";
|
|
||||||
import { S } from "deka-dom-el/signals";
|
|
||||||
const { parse }= globalThis.BFS || { parse(){ return { children: [ "not implemented" ] } } };
|
|
||||||
// Example HTML snippets
|
|
||||||
const examples = [
|
|
||||||
{
|
|
||||||
name: "Simple Component",
|
|
||||||
html: `<div class="card">
|
|
||||||
<img src="image.jpg" alt="Card Image" class="card-image">
|
|
||||||
<h2 class="card-title">Card Title</h2>
|
|
||||||
<p class="card-text">This is a simple card component</p>
|
|
||||||
<button aria-pressed="mixed" type="button" class="card-button">Click Me</button>
|
|
||||||
</div>`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Navigation",
|
|
||||||
html: `<nav class="main-nav">
|
|
||||||
<ul>
|
|
||||||
<li><a href="/" class="active">Home</a></li>
|
|
||||||
<li><a href="/about">About</a></li>
|
|
||||||
<li><a href="/services">Services</a></li>
|
|
||||||
<li><a href="/contact">Contact</a></li>
|
|
||||||
</ul>
|
|
||||||
</nav>`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Form",
|
|
||||||
html: `<form class="contact-form" onsubmit="submitForm(event)">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="name">Name:</label>
|
|
||||||
<input type="text" id="name" name="name" required>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="email">Email:</label>
|
|
||||||
<input type="email" id="email" name="email" required>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="message">Message:</label>
|
|
||||||
<textarea id="message" name="message" rows="4" required></textarea>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="submit-btn">Send Message</button>
|
|
||||||
</form>`
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Convert HTML to dd<el> code
|
|
||||||
function convertHTMLtoDDE(html, options = {}) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
const parsed = parse(html);
|
|
||||||
const content = parsed.children[0] || parsed.childNodes[0];
|
|
||||||
return !content ? "" : nodeToDDE(content, options);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Parsing error:", error);
|
|
||||||
return `// Error parsing HTML: ${error.message}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Node types based on standard DOM nodeType values
|
|
||||||
const NODE_TYPE = {
|
|
||||||
ELEMENT: 1, // Standard element node (equivalent to node.type === "element")
|
|
||||||
TEXT: 3, // Text node (equivalent to node.type === "text")
|
|
||||||
COMMENT: 8 // Comment node (equivalent to node.type === "comment")
|
|
||||||
};
|
|
||||||
|
|
||||||
// Convert a parsed node to dd<el> code
|
|
||||||
function nodeToDDE(node, options = {}, level = 0) {
|
|
||||||
const tab= options.indent === "-1" ? "\t" : " ".repeat(options.indent);
|
|
||||||
const indent = tab.repeat(level);
|
|
||||||
const nextIndent = tab.repeat(level + 1);
|
|
||||||
|
|
||||||
const { nodeType } = node;
|
|
||||||
// Handle text nodes
|
|
||||||
if (nodeType === NODE_TYPE.TEXT) {
|
|
||||||
const text = el("i", { innerText: node.nodeValue }).textContent;
|
|
||||||
if (!text.trim()) return null;
|
|
||||||
|
|
||||||
// Return as plain text or template string for longer text
|
|
||||||
return text.includes("\n") || text.includes('"')
|
|
||||||
? `\`${text}\``
|
|
||||||
: `"${text}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle comment nodes
|
|
||||||
if (nodeType === NODE_TYPE.COMMENT) {
|
|
||||||
const text = node.nodeValue;
|
|
||||||
if (!text.trim()) return null;
|
|
||||||
return text.includes("\n")
|
|
||||||
? [ "/*", ...text.trim().split("\n").map(l=> tab+l), "*/" ]
|
|
||||||
: [ `// ${text}` ];
|
|
||||||
}
|
|
||||||
|
|
||||||
// For element nodes
|
|
||||||
if (nodeType === NODE_TYPE.ELEMENT) {
|
|
||||||
// Special case for SVG elements
|
|
||||||
const isNS = node.tagName === "svg";
|
|
||||||
const elFunction = isNS ? "elNS" : "el";
|
|
||||||
|
|
||||||
// Get tag name
|
|
||||||
let tagStr = `"${node.tagName}"`;
|
|
||||||
|
|
||||||
// Process attributes
|
|
||||||
const attrs = [];
|
|
||||||
const sets = {
|
|
||||||
aria: {},
|
|
||||||
data: {},
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const { name: key, value } of node.attributes) {
|
|
||||||
// Handle class attribute
|
|
||||||
if (key === "class") {
|
|
||||||
attrs.push(`className: "${value}"`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle style attribute
|
|
||||||
if (key === "style") {
|
|
||||||
if (options.styleAsObject) {
|
|
||||||
// Convert inline style to object
|
|
||||||
const styleObj = {};
|
|
||||||
value.split(";").forEach(part => {
|
|
||||||
const [propRaw, valueRaw] = part.split(":");
|
|
||||||
if (propRaw && valueRaw) {
|
|
||||||
const prop = propRaw.trim();
|
|
||||||
const propCamel = prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
||||||
styleObj[propCamel] = valueRaw.trim();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Object.keys(styleObj).length > 0) {
|
|
||||||
const styleStr = JSON.stringify(styleObj).replace(/"([^"]+)":/g, "$1:");
|
|
||||||
attrs.push(`style: ${styleStr}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Keep as string
|
|
||||||
attrs.push(`style: "${value}"`);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle boolean attributes
|
|
||||||
if (value === "" || value === key) {
|
|
||||||
attrs.push(`${key}: true`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle data/aria attributes
|
|
||||||
if (key.startsWith("data-") || key.startsWith("aria-")) {
|
|
||||||
const keyName = key.startsWith("aria-") ? "aria" : "data";
|
|
||||||
const keyCamel = key.slice(keyName.length + 1).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
||||||
sets[keyName][keyCamel] = value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regular attributes
|
|
||||||
const keyRegular = key==="for"
|
|
||||||
? "htmlFor"
|
|
||||||
: key.startsWith("on")
|
|
||||||
? `"=${key}"`
|
|
||||||
: key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
||||||
attrs.push(`${keyRegular}: "${value}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process sets
|
|
||||||
for (const [name, set] of Object.entries(sets)) {
|
|
||||||
if(options.dataAttrsAsCamel)
|
|
||||||
for (const [key, value] of Object.entries(set))
|
|
||||||
attrs.push(`${name}${key[0].toUpperCase() + key.substring(1)}: "${value}"`);
|
|
||||||
else {
|
|
||||||
const setStr= Object.entries(set).map(([key, value]) => `${key}: "${value}"`).join(",");
|
|
||||||
if (setStr !== "")
|
|
||||||
attrs.push(`${name}set: { ${setStr} }`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process children
|
|
||||||
const children = [];
|
|
||||||
for (const child of node.childNodes) {
|
|
||||||
const childCode = nodeToDDE(child, options, level + 1);
|
|
||||||
if (!childCode) continue;
|
|
||||||
|
|
||||||
children.push(childCode);
|
|
||||||
}
|
|
||||||
if(node.childNodes.length===1 && node.childNodes[0].nodeType===NODE_TYPE.TEXT){
|
|
||||||
const textContent= children.pop().slice(1, -1);
|
|
||||||
attrs.unshift(`textContent: "${textContent}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the element creation code
|
|
||||||
let result = `${elFunction}("${node.tagName.toLowerCase()}"`;
|
|
||||||
|
|
||||||
// Add attributes if any
|
|
||||||
if (attrs.length > 0) {
|
|
||||||
const tooLong= attrs.join(``).length+result.length > 55;
|
|
||||||
if(options.expaned || tooLong || attrs.length > 3)
|
|
||||||
result += `, {\n${nextIndent}${attrs.join(`,\n${nextIndent}`)},\n${indent}}`;
|
|
||||||
else
|
|
||||||
result += `, { ${attrs.join(", ")} }`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add children if any
|
|
||||||
if (children.length > 0) {
|
|
||||||
const chs= children.map(ch=>
|
|
||||||
Array.isArray(ch) ? ch.map(l=> nextIndent + l).join("\n") :
|
|
||||||
nextIndent + ch + ",");
|
|
||||||
result += `).append(\n${chs.join("\n")}\n${indent})`;
|
|
||||||
} else {
|
|
||||||
result += ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function converter() {
|
|
||||||
// State for the converter
|
|
||||||
const htmlInput = S(examples[0].html);
|
|
||||||
const error = S("");
|
|
||||||
|
|
||||||
const status = S("");
|
|
||||||
const showStatus= msg => {
|
|
||||||
status.set(msg);
|
|
||||||
// Clear status after 3 seconds
|
|
||||||
setTimeout(() => status.set(""), 3000);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Options state
|
|
||||||
const options = {
|
|
||||||
styleAsObject: {
|
|
||||||
title: "Convert style to object",
|
|
||||||
value: S(true),
|
|
||||||
},
|
|
||||||
dataAttrsAsCamel: {
|
|
||||||
title: "dataKey/ariaKey (or dataset/ariaset)",
|
|
||||||
value: S(true),
|
|
||||||
},
|
|
||||||
indent: {
|
|
||||||
title: "Indentation (-1 for tabs)",
|
|
||||||
value: S("-1"),
|
|
||||||
type: "number",
|
|
||||||
},
|
|
||||||
expaned: {
|
|
||||||
title: "Force multiline",
|
|
||||||
value: S(false),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const getOptions = ()=> Object.fromEntries(Object.entries(options)
|
|
||||||
.map(([key, option]) => ([
|
|
||||||
key,
|
|
||||||
option.value.get()
|
|
||||||
]))
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update the dd<el> output when input or options change
|
|
||||||
const ddeOutput = S(() => {
|
|
||||||
try {
|
|
||||||
const result = convertHTMLtoDDE(htmlInput.get(), getOptions());
|
|
||||||
error.set("");
|
|
||||||
return result;
|
|
||||||
} catch (err) {
|
|
||||||
error.set(`Error: ${err.message}`);
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Event handlers
|
|
||||||
const onConvert = on("submit", e => {
|
|
||||||
e.preventDefault();
|
|
||||||
htmlInput.set(htmlInput.get(), true);
|
|
||||||
showStatus("Converted!");
|
|
||||||
});
|
|
||||||
|
|
||||||
const onCopy = on("click", async () => {
|
|
||||||
if (!ddeOutput.get()) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(ddeOutput.get());
|
|
||||||
showStatus("Copied to clipboard!");
|
|
||||||
} catch (err) {
|
|
||||||
error.set(`Could not copy: ${err.message}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const onClear = on("click", () => {
|
|
||||||
htmlInput.set("");
|
|
||||||
showStatus("Input cleared");
|
|
||||||
});
|
|
||||||
const onExampleLoad = (example) => on("click", () => {
|
|
||||||
htmlInput.set(example.html);
|
|
||||||
showStatus(`Loaded "${example.name}" example`);
|
|
||||||
});
|
|
||||||
|
|
||||||
const optionsElements = () => Object.entries(options)
|
|
||||||
.map(([key, option]) =>
|
|
||||||
el("label", { className: "option-group" }).append(
|
|
||||||
option.type==="number"
|
|
||||||
? el("input", {
|
|
||||||
type: option.type || "checkbox",
|
|
||||||
name: key,
|
|
||||||
value: option.value.get(),
|
|
||||||
max: 10,
|
|
||||||
}, on("change", e => option.value.set(e.target.value)))
|
|
||||||
: el("input", {
|
|
||||||
type: option.type || "checkbox",
|
|
||||||
name: key,
|
|
||||||
checked: option.value.get(),
|
|
||||||
}, on("change", e => option.value.set(e.target.checked))),
|
|
||||||
option.title,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const exampleButtons = examples.map(example =>
|
|
||||||
el("button", {
|
|
||||||
type: "button",
|
|
||||||
className: "secondary example-button"
|
|
||||||
}, onExampleLoad(example)).append(example.name)
|
|
||||||
);
|
|
||||||
|
|
||||||
return el("div", { id: "html-to-dde-converter" }).append(
|
|
||||||
el("h3", "HTML to dd<el> Converter"),
|
|
||||||
el("p", { className: "description" }).append(
|
|
||||||
"Convert HTML markup to dd<el> JavaScript code. Paste your HTML below or choose from an example."
|
|
||||||
),
|
|
||||||
|
|
||||||
el("form", { className: "converter-form" }, onConvert).append(
|
|
||||||
el("div", { className: "options" }).append(...optionsElements()),
|
|
||||||
|
|
||||||
el("div", { className: "examples-list" }).append(
|
|
||||||
el("label", "Examples: "),
|
|
||||||
...exampleButtons
|
|
||||||
),
|
|
||||||
|
|
||||||
el("div", { className: "editor-container" }).append(
|
|
||||||
el("div", { className: "input-group" }).append(
|
|
||||||
el("label", { htmlFor: "html-input" }).append(
|
|
||||||
"HTML Input",
|
|
||||||
el("div", { className: "button-group" }).append(
|
|
||||||
el("button", {
|
|
||||||
type: "button",
|
|
||||||
className: "secondary",
|
|
||||||
title: "Clear input"
|
|
||||||
}, onClear).append("Clear")
|
|
||||||
)
|
|
||||||
),
|
|
||||||
el("textarea", {
|
|
||||||
id: "html-input",
|
|
||||||
spellcheck: false,
|
|
||||||
value: htmlInput,
|
|
||||||
placeholder: "Paste your HTML here or choose an example",
|
|
||||||
oninput: e => htmlInput.set(e.target.value)
|
|
||||||
})
|
|
||||||
),
|
|
||||||
|
|
||||||
el("div", { className: "output-group" }).append(
|
|
||||||
el("label", { htmlFor: "dde-output" }).append(
|
|
||||||
"dd<el> Output",
|
|
||||||
el("div", { className: "button-group" }).append(
|
|
||||||
el("button", {
|
|
||||||
textContent: "Copy",
|
|
||||||
type: "button",
|
|
||||||
className: "copy-button",
|
|
||||||
title: "Copy to clipboard",
|
|
||||||
disabled: S(() => !ddeOutput.get())
|
|
||||||
}, onCopy)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
el("textarea", {
|
|
||||||
id: "dde-output",
|
|
||||||
readonly: true,
|
|
||||||
spellcheck: false,
|
|
||||||
placeholder: "The converted dd<el> code will appear here",
|
|
||||||
value: S(() => ddeOutput.get() || "// Convert HTML to see results here")
|
|
||||||
})
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
el("div", { className: "button-group" }).append(
|
|
||||||
S.el(error, error => !error ? el() : el("div", { className: "error" }).append(error)),
|
|
||||||
el("div", { className: "status", textContent: status }),
|
|
||||||
el("button", { type: "submit" }).append("Convert")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,8 +1,9 @@
|
|||||||
import { page_id, styles } from "../ssr.js";
|
import { styles } from "../ssr.js";
|
||||||
const host= "."+example.name;
|
const host= "."+example.name;
|
||||||
styles.css`
|
styles.css`
|
||||||
${host} {
|
${host} {
|
||||||
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);
|
||||||
@ -83,6 +84,7 @@ 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;
|
||||||
@ -94,18 +96,6 @@ 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();
|
||||||
@ -118,15 +108,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 {"normal"|"big"} [attrs.variant="normal"] Size of the example
|
* @param {string} attrs.page_id ID of the page
|
||||||
* */
|
* */
|
||||||
export function example({ src, language= "js", variant= "normal" }){
|
export function example({ src, language= "js", page_id }){
|
||||||
registerClientPart(page_id);
|
registerClientPart(page_id);
|
||||||
const content= s.cat(src).toString()
|
const content= s.cat(src).toString()
|
||||||
.replaceAll(/ from "deka-dom-el(\/signals)?";/g, ' from "./esm-with-signals.js";');
|
.replaceAll(/ from "deka-dom-el(\/signals)?";/g, ' from "./esm-with-signals.js";');
|
||||||
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=> el.dataset.variant= variant),
|
el(code, { id, content, language, className: example.name }),
|
||||||
elCode({ id, content, extension: "."+language })
|
elCode({ id, content, extension: "."+language })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,375 +0,0 @@
|
|||||||
/**
|
|
||||||
* Case Study: Data Dashboard with Charts
|
|
||||||
*
|
|
||||||
* This example demonstrates:
|
|
||||||
* - Integration with a third-party charting library
|
|
||||||
* - Data fetching and state management
|
|
||||||
* - Responsive layout design
|
|
||||||
* - Multiple interactive components working together
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { el, on } from "deka-dom-el";
|
|
||||||
import { S } from "deka-dom-el/signals";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data Dashboard Component with Chart Integration
|
|
||||||
* @returns {HTMLElement} Dashboard element
|
|
||||||
*/
|
|
||||||
export function DataDashboard() {
|
|
||||||
// Mock data for demonstration
|
|
||||||
const DATA = {
|
|
||||||
sales: [42, 58, 65, 49, 72, 85, 63, 70, 78, 89, 95, 86],
|
|
||||||
visitors: [1420, 1620, 1750, 1850, 2100, 2400, 2250, 2500, 2750, 2900, 3100, 3200],
|
|
||||||
conversion: [2.9, 3.5, 3.7, 2.6, 3.4, 3.5, 2.8, 2.8, 2.8, 3.1, 3.0, 2.7],
|
|
||||||
months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
|
||||||
};
|
|
||||||
const years = [2022, 2023, 2024];
|
|
||||||
const dataTypes = [
|
|
||||||
{ id: 'sales', label: 'Sales', unit: 'K' },
|
|
||||||
{ id: 'visitors', label: 'Visitors', unit: '' },
|
|
||||||
{ id: 'conversion', label: 'Conversion Rate', unit: '%' }
|
|
||||||
];
|
|
||||||
|
|
||||||
// Filter options
|
|
||||||
const selectedYear = S(2024);
|
|
||||||
const onYearChange = on("change", e => {
|
|
||||||
selectedYear.set(parseInt(/** @type {HTMLSelectElement} */(e.target).value));
|
|
||||||
loadData();
|
|
||||||
});
|
|
||||||
const selectedDataType = S(/** @type {'sales' | 'visitors' | 'conversion'} */ ('sales'));
|
|
||||||
const onDataTypeChange = on("click", e => {
|
|
||||||
const type = /** @type {'sales' | 'visitors' | 'conversion'} */(
|
|
||||||
/** @type {HTMLButtonElement} */(e.currentTarget).dataset.type);
|
|
||||||
selectedDataType.set(type);
|
|
||||||
});
|
|
||||||
const currentDataType = S(() => dataTypes.find(type => type.id === selectedDataType.get()));
|
|
||||||
const selectedData = S(() => DATA[selectedDataType.get()]);
|
|
||||||
|
|
||||||
// Values based on filters
|
|
||||||
const totalValue = S(() => selectedData.get().reduce((sum, value) => sum + value, 0));
|
|
||||||
const averageValue = S(() => {
|
|
||||||
const data = selectedData.get();
|
|
||||||
return data.reduce((sum, value) => sum + value, 0) / data.length;
|
|
||||||
});
|
|
||||||
const highestValue = S(() => Math.max(...selectedData.get()));
|
|
||||||
|
|
||||||
// Simulate data loading
|
|
||||||
const isLoading = S(false);
|
|
||||||
const error = S(null);
|
|
||||||
function loadData() {
|
|
||||||
isLoading.set(true);
|
|
||||||
error.set(null);
|
|
||||||
|
|
||||||
// Simulate API call
|
|
||||||
setTimeout(() => {
|
|
||||||
if (Math.random() > 0.9) {
|
|
||||||
// Simulate occasional error
|
|
||||||
error.set('Failed to load data. Please try again.');
|
|
||||||
}
|
|
||||||
isLoading.set(false);
|
|
||||||
}, 800);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reactive chart rendering
|
|
||||||
const chart = S(()=> {
|
|
||||||
const chart= el("canvas", { id: "chart-canvas", width: 800, height: 400 });
|
|
||||||
const ctx = chart.getContext('2d');
|
|
||||||
const data = selectedData.get();
|
|
||||||
const months = DATA.months;
|
|
||||||
const width = chart.width;
|
|
||||||
const height = chart.height;
|
|
||||||
const maxValue = Math.max(...data) * 1.1;
|
|
||||||
const barWidth = width / data.length - 10;
|
|
||||||
|
|
||||||
// Clear canvas
|
|
||||||
ctx.clearRect(0, 0, width, height);
|
|
||||||
|
|
||||||
// Draw background grid
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.strokeStyle = '#f0f0f0';
|
|
||||||
ctx.lineWidth = 1;
|
|
||||||
for(let i = 0; i < 5; i++) {
|
|
||||||
const y = height - (height * (i / 5)) - 30;
|
|
||||||
ctx.moveTo(50, y);
|
|
||||||
ctx.lineTo(width - 20, y);
|
|
||||||
|
|
||||||
// Draw grid labels
|
|
||||||
ctx.fillStyle = '#999';
|
|
||||||
ctx.font = '12px Arial';
|
|
||||||
ctx.fillText(Math.round(maxValue * (i / 5)).toString(), 20, y + 5);
|
|
||||||
}
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
// Draw bars
|
|
||||||
data.forEach((value, index) => {
|
|
||||||
const x = index * (barWidth + 10) + 60;
|
|
||||||
const barHeight = (value / maxValue) * (height - 60);
|
|
||||||
|
|
||||||
// Bar
|
|
||||||
ctx.fillStyle = '#4a90e2';
|
|
||||||
ctx.fillRect(x, height - barHeight - 30, barWidth, barHeight);
|
|
||||||
|
|
||||||
// Month label
|
|
||||||
ctx.fillStyle = '#666';
|
|
||||||
ctx.font = '12px Arial';
|
|
||||||
ctx.fillText(months[index], x + barWidth/2 - 10, height - 10);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Chart title
|
|
||||||
ctx.fillStyle = '#333';
|
|
||||||
ctx.font = 'bold 14px Arial';
|
|
||||||
ctx.fillText(`${currentDataType.get().label} (${selectedYear.get()})`, width/2 - 80, 20);
|
|
||||||
return chart;
|
|
||||||
});
|
|
||||||
|
|
||||||
return el("div", { className: "dashboard" }).append(
|
|
||||||
el("header", { className: "dashboard-header" }).append(
|
|
||||||
el("h1", "Sales Performance Dashboard"),
|
|
||||||
el("div", { className: "year-filter" }).append(
|
|
||||||
el("label", { htmlFor: "yearSelect", textContent: "Select Year:" }),
|
|
||||||
el("select", { id: "yearSelect" },
|
|
||||||
on.defer(el=> el.value = selectedYear.get().toString()),
|
|
||||||
onYearChange
|
|
||||||
).append(
|
|
||||||
...years.map(year => el("option", { value: year, textContent: year }))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
S.el(error, errorMsg => !errorMsg
|
|
||||||
? el()
|
|
||||||
: el("div", { className: "error-message" }).append(
|
|
||||||
el("p", errorMsg),
|
|
||||||
el("button", { textContent: "Retry", type: "button" }, on("click", loadData)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
S.el(isLoading, loading => !loading
|
|
||||||
? el()
|
|
||||||
: el("div", { className: "loading-spinner" })
|
|
||||||
),
|
|
||||||
|
|
||||||
// Main dashboard content
|
|
||||||
el("div", { className: "dashboard-content" }).append(
|
|
||||||
// Metrics cards
|
|
||||||
el("div", { className: "metrics-container" }).append(
|
|
||||||
el("div", { className: "metric-card" }).append(
|
|
||||||
el("h3", "Total"),
|
|
||||||
el("#text", S(() => `${totalValue.get().toLocaleString()}${currentDataType.get().unit}`)),
|
|
||||||
),
|
|
||||||
el("div", { className: "metric-card" }).append(
|
|
||||||
el("h3", "Average"),
|
|
||||||
el("#text", S(() => `${averageValue.get().toFixed(1)}${currentDataType.get().unit}`)),
|
|
||||||
),
|
|
||||||
el("div", { className: "metric-card" }).append(
|
|
||||||
el("h3", "Highest"),
|
|
||||||
el("#text", S(() => `${highestValue.get()}${currentDataType.get().unit}`)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Data type selection tabs
|
|
||||||
el("div", { className: "data-type-tabs" }).append(
|
|
||||||
...dataTypes.map(type =>
|
|
||||||
el("button", {
|
|
||||||
type: "button",
|
|
||||||
className: S(() => selectedDataType.get() === type.id ? 'active' : ''),
|
|
||||||
dataType: type.id,
|
|
||||||
textContent: type.label
|
|
||||||
}, onDataTypeChange)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
// Chart container
|
|
||||||
el("div", { className: "chart-container" }).append(
|
|
||||||
S.el(chart, chart => chart)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render the component
|
|
||||||
document.body.append(
|
|
||||||
el("div", { style: "padding: 20px; background: #f5f5f5; min-height: 100vh;" }).append(
|
|
||||||
el(DataDashboard)
|
|
||||||
),
|
|
||||||
el("style", `
|
|
||||||
.dashboard {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
||||||
max-width: 1000px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 1rem;
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-header h1 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
margin: 0;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.year-filter {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.year-filter select {
|
|
||||||
padding: 0.5rem;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metrics-container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 1rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metric-card {
|
|
||||||
background: #f9f9f9;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 1rem;
|
|
||||||
text-align: center;
|
|
||||||
transition: transform 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metric-card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.metric-card h3 {
|
|
||||||
margin-top: 0;
|
|
||||||
color: #666;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metric-card p {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-type-tabs {
|
|
||||||
display: flex;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-type-tabs button {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
padding: 0.75rem 1.5rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #666;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-type-tabs button.active {
|
|
||||||
color: #4a90e2;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-type-tabs button.active::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
bottom: -1px;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 3px;
|
|
||||||
background: #4a90e2;
|
|
||||||
border-radius: 3px 3px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart-container {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 1rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
);
|
|
@ -1,412 +0,0 @@
|
|||||||
/**
|
|
||||||
* Case Study: Interactive Image Gallery
|
|
||||||
*
|
|
||||||
* This example demonstrates:
|
|
||||||
* - Dynamic loading of content
|
|
||||||
* - Lightbox functionality
|
|
||||||
* - Animation handling
|
|
||||||
* - Keyboard and gesture navigation
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { el, memo, on } from "deka-dom-el";
|
|
||||||
import { S } from "deka-dom-el/signals";
|
|
||||||
|
|
||||||
// Sample image data
|
|
||||||
const imagesSample = (url=> [
|
|
||||||
{ id: 1, src: url+'nature', alt: 'Nature', title: 'Beautiful Landscape' },
|
|
||||||
{ id: 2, src: url+'places', alt: 'City', title: 'Urban Architecture' },
|
|
||||||
{ id: 3, src: url+'people', alt: 'People', title: 'Street Photography' },
|
|
||||||
{ id: 4, src: url+'food', alt: 'Food', title: 'Culinary Delights' },
|
|
||||||
{ id: 5, src: url+'animals', alt: 'Animals', title: 'Wildlife' },
|
|
||||||
{ id: 6, src: url+'travel', alt: 'Travel', title: 'Adventure Awaits' },
|
|
||||||
{ id: 7, src: url+'computer', alt: 'Technology', title: 'Modern Tech' },
|
|
||||||
{ id: 8, src: url+'music', alt: 'Art', title: 'Creative Expression' },
|
|
||||||
])('https://api.algobook.info/v1/randomimage?category=');
|
|
||||||
/**
|
|
||||||
* Interactive Image Gallery Component
|
|
||||||
* @returns {HTMLElement} Gallery element
|
|
||||||
*/
|
|
||||||
export function ImageGallery(images= imagesSample) {
|
|
||||||
const filterTag = S('all');
|
|
||||||
const imagesToDisplay = S(() => {
|
|
||||||
const tag = filterTag.get();
|
|
||||||
if (tag === 'all') return images;
|
|
||||||
else return images.filter(img => img.alt.toLowerCase() === tag);
|
|
||||||
})
|
|
||||||
const onFilterChange = tag => on("click", () => {
|
|
||||||
filterTag.set(tag);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Lightbox
|
|
||||||
const selectedImageId = S(null);
|
|
||||||
const selectedImage = S(() => {
|
|
||||||
const id = selectedImageId.get();
|
|
||||||
return id ? images.find(img => img.id === id) : null;
|
|
||||||
});
|
|
||||||
const isLightboxOpen = S(() => selectedImage.get() !== null);
|
|
||||||
const onImageClick = id => on("click", () => {
|
|
||||||
selectedImageId.set(id);
|
|
||||||
document.body.style.overflow = 'hidden'; // Prevent scrolling when lightbox is open
|
|
||||||
|
|
||||||
// Add keyboard event listeners when lightbox opens
|
|
||||||
document.addEventListener('keydown', handleKeyDown);
|
|
||||||
});
|
|
||||||
const closeLightbox = () => {
|
|
||||||
selectedImageId.set(null);
|
|
||||||
document.body.style.overflow = ''; // Restore scrolling
|
|
||||||
|
|
||||||
// Remove keyboard event listeners when lightbox closes
|
|
||||||
document.removeEventListener('keydown', handleKeyDown);
|
|
||||||
};
|
|
||||||
const onPrevImage = e => {
|
|
||||||
e.stopPropagation(); // Prevent closing the lightbox
|
|
||||||
const images = imagesToDisplay.get();
|
|
||||||
const currentId = selectedImageId.get();
|
|
||||||
const currentIndex = images.findIndex(img => img.id === currentId);
|
|
||||||
const prevIndex = (currentIndex - 1 + images.length) % images.length;
|
|
||||||
selectedImageId.set(images[prevIndex].id);
|
|
||||||
};
|
|
||||||
const onNextImage = e => {
|
|
||||||
e.stopPropagation(); // Prevent closing the lightbox
|
|
||||||
const images = imagesToDisplay.get();
|
|
||||||
const currentId = selectedImageId.get();
|
|
||||||
const currentIndex = images.findIndex(img => img.id === currentId);
|
|
||||||
const nextIndex = (currentIndex + 1) % images.length;
|
|
||||||
selectedImageId.set(images[nextIndex].id);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Keyboard navigation handler
|
|
||||||
function handleKeyDown(e) {
|
|
||||||
switch(e.key) {
|
|
||||||
case 'Escape':
|
|
||||||
closeLightbox();
|
|
||||||
break;
|
|
||||||
case 'ArrowLeft':
|
|
||||||
document.querySelector('.lightbox-prev-btn').click();
|
|
||||||
break;
|
|
||||||
case 'ArrowRight':
|
|
||||||
document.querySelector('.lightbox-next-btn').click();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the gallery UI
|
|
||||||
return el("div", { className: "gallery-container" }).append(
|
|
||||||
// Gallery header
|
|
||||||
el("header", { className: "gallery-header" }).append(
|
|
||||||
el("h1", "Interactive Image Gallery"),
|
|
||||||
el("p", "Click on any image to view it in the lightbox. Use arrow keys for navigation.")
|
|
||||||
),
|
|
||||||
|
|
||||||
// Filter options
|
|
||||||
el("div", { className: "gallery-filters" }).append(
|
|
||||||
el("button", {
|
|
||||||
classList: { active: S(() => filterTag.get() === 'all') },
|
|
||||||
textContent: "All"
|
|
||||||
}, onFilterChange('all')),
|
|
||||||
el("button", {
|
|
||||||
classList: { active: S(() => filterTag.get() === 'nature') },
|
|
||||||
textContent: "Nature"
|
|
||||||
}, onFilterChange('nature')),
|
|
||||||
el("button", {
|
|
||||||
classList: { active: S(() => filterTag.get() === 'urban') },
|
|
||||||
textContent: "Urban"
|
|
||||||
}, onFilterChange('urban')),
|
|
||||||
el("button", {
|
|
||||||
classList: { active: S(() => filterTag.get() === 'people') },
|
|
||||||
textContent: "People"
|
|
||||||
}, onFilterChange('people'))
|
|
||||||
),
|
|
||||||
|
|
||||||
// Image grid
|
|
||||||
el("div", { className: "gallery-grid" }).append(
|
|
||||||
S.el(imagesToDisplay, images =>
|
|
||||||
images.map(image =>
|
|
||||||
memo(image.id, ()=>
|
|
||||||
el("div", {
|
|
||||||
className: "gallery-item",
|
|
||||||
dataTag: image.alt.toLowerCase()
|
|
||||||
}).append(
|
|
||||||
el("img", {
|
|
||||||
src: image.src,
|
|
||||||
alt: image.alt,
|
|
||||||
loading: "lazy"
|
|
||||||
}, onImageClick(image.id)),
|
|
||||||
el("div", { className: "gallery-item-caption" }).append(
|
|
||||||
el("h3", image.title),
|
|
||||||
el("p", image.alt)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
// Lightbox (only shown when an image is selected)
|
|
||||||
S.el(isLightboxOpen, open => !open
|
|
||||||
? el()
|
|
||||||
: el("div", { className: "lightbox-overlay" }, on("click", closeLightbox)).append(
|
|
||||||
el("div", {
|
|
||||||
className: "lightbox-content",
|
|
||||||
onClick: e => e.stopPropagation() // Prevent closing when clicking inside
|
|
||||||
}).append(
|
|
||||||
el("button", {
|
|
||||||
className: "lightbox-close-btn",
|
|
||||||
"aria-label": "Close lightbox"
|
|
||||||
}, on("click", closeLightbox)).append("×"),
|
|
||||||
|
|
||||||
el("button", {
|
|
||||||
className: "lightbox-prev-btn",
|
|
||||||
"aria-label": "Previous image"
|
|
||||||
}, on("click", onPrevImage)).append("❮"),
|
|
||||||
|
|
||||||
el("button", {
|
|
||||||
className: "lightbox-next-btn",
|
|
||||||
"aria-label": "Next image"
|
|
||||||
}, on("click", onNextImage)).append("❯"),
|
|
||||||
|
|
||||||
S.el(selectedImage, img => !img
|
|
||||||
? el()
|
|
||||||
: el("div", { className: "lightbox-image-container" }).append(
|
|
||||||
el("img", {
|
|
||||||
src: img.src,
|
|
||||||
alt: img.alt,
|
|
||||||
className: "lightbox-image"
|
|
||||||
}),
|
|
||||||
el("div", { className: "lightbox-caption" }).append(
|
|
||||||
el("h2", img.title),
|
|
||||||
el("p", img.alt)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render the component
|
|
||||||
document.body.append(
|
|
||||||
el("div", { style: "padding: 20px; background: #f5f5f5; min-height: 100vh;" }).append(
|
|
||||||
el(ImageGallery)
|
|
||||||
),
|
|
||||||
el("style", `
|
|
||||||
.gallery-container {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-header h1 {
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-header p {
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-filters {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-filters button {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
padding: 0.5rem 1.5rem;
|
|
||||||
margin: 0 0.5rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 30px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-filters button:hover {
|
|
||||||
background: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-filters button.active {
|
|
||||||
background: #4a90e2;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-item {
|
|
||||||
position: relative;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
|
|
||||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-item:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-item img {
|
|
||||||
width: 100%;
|
|
||||||
height: 200px;
|
|
||||||
object-fit: cover;
|
|
||||||
display: block;
|
|
||||||
transition: transform 0.5s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-item:hover img {
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-item-caption {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
|
|
||||||
color: white;
|
|
||||||
padding: 1rem;
|
|
||||||
transform: translateY(100%);
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-item:hover .gallery-item-caption {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-item-caption h3 {
|
|
||||||
margin: 0 0 0.5rem;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-item-caption p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Lightbox styles */
|
|
||||||
.lightbox-overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.9);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
z-index: 1000;
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightbox-content {
|
|
||||||
position: relative;
|
|
||||||
max-width: 90%;
|
|
||||||
max-height: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightbox-image-container {
|
|
||||||
overflow: hidden;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 0 30px rgba(0, 0, 0, 0.5);
|
|
||||||
background: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightbox-image {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 80vh;
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightbox-caption {
|
|
||||||
background: #222;
|
|
||||||
color: white;
|
|
||||||
padding: 1rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightbox-caption h2 {
|
|
||||||
margin: 0 0 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightbox-caption p {
|
|
||||||
margin: 0;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightbox-close-btn,
|
|
||||||
.lightbox-prev-btn,
|
|
||||||
.lightbox-next-btn {
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.3s ease;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightbox-close-btn:hover,
|
|
||||||
.lightbox-prev-btn:hover,
|
|
||||||
.lightbox-next-btn:hover {
|
|
||||||
background: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightbox-close-btn {
|
|
||||||
top: -25px;
|
|
||||||
right: -25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightbox-prev-btn {
|
|
||||||
left: -25px;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightbox-next-btn {
|
|
||||||
right: -25px;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.gallery-container {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-grid {
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightbox-prev-btn,
|
|
||||||
.lightbox-next-btn {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
);
|
|
@ -1,339 +0,0 @@
|
|||||||
/**
|
|
||||||
* Case Study: Interactive Form with Validation
|
|
||||||
*
|
|
||||||
* This example demonstrates:
|
|
||||||
* - Form handling with real-time validation
|
|
||||||
* - Reactive UI updates based on input state
|
|
||||||
* - Complex form state management
|
|
||||||
* - Clean separation of concerns (data, validation, UI)
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { dispatchEvent, el, on, scope } from "deka-dom-el";
|
|
||||||
import { S } from "deka-dom-el/signals";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} FormState
|
|
||||||
* @property {string} name
|
|
||||||
* @property {string} email
|
|
||||||
* @property {string} password
|
|
||||||
* @property {string} confirmPassword
|
|
||||||
* @property {boolean} agreedToTerms
|
|
||||||
* */
|
|
||||||
/**
|
|
||||||
* Interactive Form with Validation Component
|
|
||||||
* @returns {HTMLElement} Form element
|
|
||||||
*/
|
|
||||||
export function InteractiveForm() {
|
|
||||||
const submitted = S(false);
|
|
||||||
/** @type {FormState|null} */
|
|
||||||
let formState = null;
|
|
||||||
/** @param {CustomEvent<FormState>} event */
|
|
||||||
const onSubmit = ({ detail }) => {
|
|
||||||
submitted.set(true);
|
|
||||||
formState = detail;
|
|
||||||
};
|
|
||||||
const onAnotherAccount = () => {
|
|
||||||
submitted.set(false)
|
|
||||||
formState = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
return el("div", { className: "form-container" }).append(
|
|
||||||
S.el(submitted, s => s
|
|
||||||
? el("div", { className: "success-message" }).append(
|
|
||||||
el("h3", "Thank you for registering!"),
|
|
||||||
el("p", `Welcome, ${formState.name}! Your account has been created successfully.`),
|
|
||||||
el("button", { textContent: "Register another account", type: "button" },
|
|
||||||
on("click", onAnotherAccount)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: el(Form, { initial: formState }, on("form:submit", onSubmit))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Form Component
|
|
||||||
* @type {(props: { initial: FormState | null }) => HTMLElement}
|
|
||||||
* */
|
|
||||||
export function Form({ initial }) {
|
|
||||||
const { host }= scope;
|
|
||||||
// Form state management
|
|
||||||
const formState = S(initial || {
|
|
||||||
name: '',
|
|
||||||
email: '',
|
|
||||||
password: '',
|
|
||||||
confirmPassword: '',
|
|
||||||
agreedToTerms: false
|
|
||||||
}, {
|
|
||||||
/**
|
|
||||||
* @template {keyof FormState} K
|
|
||||||
* @param {K} key
|
|
||||||
* @param {FormState[K]} value
|
|
||||||
* */
|
|
||||||
update(key, value) {
|
|
||||||
this.value[key] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
/**
|
|
||||||
* Event handler for input events
|
|
||||||
* @param {"value"|"checked"} prop
|
|
||||||
* @returns {(ev: Event) => void}
|
|
||||||
* */
|
|
||||||
const onChange= prop => ev => {
|
|
||||||
const input = /** @type {HTMLInputElement} */(ev.target);
|
|
||||||
S.action(formState, "update", /** @type {keyof FormState} */(input.id), input[prop]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Form validate state
|
|
||||||
const nameValid = S(() => formState.get().name.length >= 3);
|
|
||||||
const emailValid = S(() => {
|
|
||||||
const email = formState.get().email;
|
|
||||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
||||||
});
|
|
||||||
const passwordValid = S(() => {
|
|
||||||
const password = formState.get().password;
|
|
||||||
return password.length >= 8 && /[A-Z]/.test(password) && /[0-9]/.test(password);
|
|
||||||
});
|
|
||||||
const passwordsMatch = S(() => {
|
|
||||||
const { password, confirmPassword } = formState.get();
|
|
||||||
return password === confirmPassword && confirmPassword !== '';
|
|
||||||
});
|
|
||||||
const termsAgreed = S(() => formState.get().agreedToTerms);
|
|
||||||
const formValid = S(() =>
|
|
||||||
nameValid.get() &&
|
|
||||||
emailValid.get() &&
|
|
||||||
passwordValid.get() &&
|
|
||||||
passwordsMatch.get() &&
|
|
||||||
termsAgreed.get()
|
|
||||||
);
|
|
||||||
|
|
||||||
const dispatcSubmit = dispatchEvent("form:submit", host);
|
|
||||||
const onSubmit = on("submit", e => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!formValid.get()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatcSubmit(formState.get());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Component UI
|
|
||||||
return el("form", { className: "registration-form" }, onSubmit).append(
|
|
||||||
el("h2", "Create an Account"),
|
|
||||||
|
|
||||||
// Name field
|
|
||||||
el("div", { classList: {
|
|
||||||
"form-group": true,
|
|
||||||
valid: nameValid,
|
|
||||||
invalid: S(()=> !nameValid.get() && formState.get().name)
|
|
||||||
}}).append(
|
|
||||||
el("label", { htmlFor: "name", textContent: "Full Name" }),
|
|
||||||
el("input", {
|
|
||||||
id: "name",
|
|
||||||
type: "text",
|
|
||||||
value: formState.get().name,
|
|
||||||
placeholder: "Enter your full name"
|
|
||||||
}, on("input", onChange("value"))),
|
|
||||||
el("div", { className: "validation-message", textContent: "Name must be at least 3 characters long" }),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Email field
|
|
||||||
el("div", { classList: {
|
|
||||||
"form-group": true,
|
|
||||||
valid: emailValid,
|
|
||||||
invalid: S(()=> !emailValid.get() && formState.get().email)
|
|
||||||
}}).append(
|
|
||||||
el("label", { htmlFor: "email", textContent: "Email Address" }),
|
|
||||||
el("input", {
|
|
||||||
id: "email",
|
|
||||||
type: "email",
|
|
||||||
value: formState.get().email,
|
|
||||||
placeholder: "Enter your email address"
|
|
||||||
}, on("input", onChange("value"))),
|
|
||||||
el("div", { className: "validation-message", textContent: "Please enter a valid email address" })
|
|
||||||
),
|
|
||||||
|
|
||||||
// Password field
|
|
||||||
el("div", { classList: {
|
|
||||||
"form-group": true,
|
|
||||||
valid: passwordValid,
|
|
||||||
invalid: S(()=> !passwordValid.get() && formState.get().password)
|
|
||||||
}}).append(
|
|
||||||
el("label", { htmlFor: "password", textContent: "Password" }),
|
|
||||||
el("input", {
|
|
||||||
id: "password",
|
|
||||||
type: "password",
|
|
||||||
value: formState.get().password,
|
|
||||||
placeholder: "Create a password"
|
|
||||||
}, on("input", onChange("value"))),
|
|
||||||
el("div", {
|
|
||||||
className: "validation-message",
|
|
||||||
textContent: "Password must be at least 8 characters with at least one uppercase letter and one number",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Confirm password field
|
|
||||||
el("div", { classList: {
|
|
||||||
"form-group": true,
|
|
||||||
valid: passwordsMatch,
|
|
||||||
invalid: S(()=> !passwordsMatch.get() && formState.get().confirmPassword)
|
|
||||||
}}).append(
|
|
||||||
el("label", { htmlFor: "confirmPassword", textContent: "Confirm Password" }),
|
|
||||||
el("input", {
|
|
||||||
id: "confirmPassword",
|
|
||||||
type: "password",
|
|
||||||
value: formState.get().confirmPassword,
|
|
||||||
placeholder: "Confirm your password"
|
|
||||||
}, on("input", onChange("value"))),
|
|
||||||
el("div", { className: "validation-message", textContent: "Passwords must match" }),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Terms agreement
|
|
||||||
el("div", { className: "form-group checkbox-group" }).append(
|
|
||||||
el("input", {
|
|
||||||
id: "agreedToTerms",
|
|
||||||
type: "checkbox",
|
|
||||||
checked: formState.get().agreedToTerms
|
|
||||||
}, on("change", onChange("checked"))),
|
|
||||||
el("label", { htmlFor: "agreedToTerms", textContent: "I agree to the Terms and Conditions" }),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Submit button
|
|
||||||
el("button", {
|
|
||||||
textContent: "Create Account",
|
|
||||||
type: "submit",
|
|
||||||
className: "submit-button",
|
|
||||||
disabled: S(() => !formValid.get())
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render the component
|
|
||||||
document.body.append(
|
|
||||||
el("div", { style: "padding: 20px; background: #f5f5f5; min-height: 100vh;" }).append(
|
|
||||||
el(InteractiveForm)
|
|
||||||
),
|
|
||||||
el("style", `
|
|
||||||
.form-container {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
||||||
max-width: 500px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
margin-top: 0;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
position: relative;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
color: #555;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="text"],
|
|
||||||
input[type="email"],
|
|
||||||
input[type="password"] {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.75rem;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 1rem;
|
|
||||||
transition: border-color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #4a90e2;
|
|
||||||
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox-group {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox-group label {
|
|
||||||
margin: 0 0 0 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.validation-message {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: #e74c3c;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
height: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
opacity: 0;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group.invalid .validation-message {
|
|
||||||
height: auto;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group.valid input {
|
|
||||||
border-color: #2ecc71;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group.invalid input {
|
|
||||||
border-color: #e74c3c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-button {
|
|
||||||
background-color: #4a90e2;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 0.75rem 1.5rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-button:hover:not(:disabled) {
|
|
||||||
background-color: #3a7bc8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-button:disabled {
|
|
||||||
background-color: #b5b5b5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success-message {
|
|
||||||
text-align: center;
|
|
||||||
color: #2ecc71;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success-message h3 {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success-message button {
|
|
||||||
background-color: #2ecc71;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 0.75rem 1.5rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success-message button:hover {
|
|
||||||
background-color: #27ae60;
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
);
|
|
@ -1,501 +0,0 @@
|
|||||||
import { el, on } from "deka-dom-el";
|
|
||||||
import { S } from "deka-dom-el/signals";
|
|
||||||
|
|
||||||
export function ProductCatalog() {
|
|
||||||
const itemsPerPage = 5;
|
|
||||||
const products = asyncSignal(S, fetchProducts, { initial: [], keepLast: true });
|
|
||||||
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 }} options - Configuration options
|
|
||||||
* @returns {Object} Status signals and control methods
|
|
||||||
*/
|
|
||||||
export function asyncSignal(S, invoker, { initial, keepLast } = {}) {
|
|
||||||
// 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: 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
);
|
|
@ -1,715 +0,0 @@
|
|||||||
/**
|
|
||||||
* Case Study: Task Manager Application
|
|
||||||
*
|
|
||||||
* This example demonstrates:
|
|
||||||
* - Complex state management with signals
|
|
||||||
* - Drag and drop functionality
|
|
||||||
* - Local storage persistence
|
|
||||||
* - Responsive design for different devices
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { el, on, dispatchEvent, scope } from "deka-dom-el";
|
|
||||||
import { S } from "deka-dom-el/signals";
|
|
||||||
|
|
||||||
/** @typedef {{ id: number, title: string, description: string, priority: string, status: string }} Task */
|
|
||||||
/**
|
|
||||||
* Task Manager Component
|
|
||||||
* @returns {HTMLElement} Task manager UI
|
|
||||||
*/
|
|
||||||
export function TaskManager() {
|
|
||||||
// <Tasks store>
|
|
||||||
const STORAGE_KEY = 'dde-task-manager';
|
|
||||||
const STATUSES = {
|
|
||||||
TODO: 'todo',
|
|
||||||
IN_PROGRESS: 'in-progress',
|
|
||||||
DONE: 'done'
|
|
||||||
};
|
|
||||||
/** @type {Task[]} */
|
|
||||||
let initialTasks = [];
|
|
||||||
try {
|
|
||||||
const saved = localStorage.getItem(STORAGE_KEY);
|
|
||||||
if (saved) {
|
|
||||||
initialTasks = JSON.parse(saved);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to load tasks from localStorage', e);
|
|
||||||
}
|
|
||||||
if (!initialTasks.length) {
|
|
||||||
initialTasks = [
|
|
||||||
{ id: 1, title: 'Create project structure', description: 'Set up folders and initial files',
|
|
||||||
status: STATUSES.DONE, priority: 'high' },
|
|
||||||
{ id: 2, title: 'Design UI components', description: 'Create mockups for main views',
|
|
||||||
status: STATUSES.IN_PROGRESS, priority: 'medium' },
|
|
||||||
{ id: 3, title: 'Implement authentication', description: 'Set up user login and registration',
|
|
||||||
status: STATUSES.TODO, priority: 'high' },
|
|
||||||
{ id: 4, title: 'Write documentation', description: 'Document API endpoints and usage examples',
|
|
||||||
status: STATUSES.TODO, priority: 'low' },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
const tasks = S(initialTasks, {
|
|
||||||
add(task) { this.value.push(task); },
|
|
||||||
remove(id) { this.value = this.value.filter(task => task.id !== id); },
|
|
||||||
update(id, task) {
|
|
||||||
const current= this.value.find(t => t.id === id);
|
|
||||||
if (current) Object.assign(current, task);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
S.on(tasks, value => {
|
|
||||||
try {
|
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(value));
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to save tasks to localStorage', e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// </Tasks store>
|
|
||||||
|
|
||||||
const filterPriority = S('all');
|
|
||||||
const searchQuery = S('');
|
|
||||||
// Filtered tasks based on priority and search query
|
|
||||||
const filteredTasks = S(() => {
|
|
||||||
let filtered = tasks.get();
|
|
||||||
|
|
||||||
// Filter by priority
|
|
||||||
if (filterPriority.get() !== 'all') {
|
|
||||||
filtered = filtered.filter(task => task.priority === filterPriority.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter by search query
|
|
||||||
const query = searchQuery.get().toLowerCase();
|
|
||||||
if (query) {
|
|
||||||
filtered = filtered.filter(task =>
|
|
||||||
task.title.toLowerCase().includes(query) ||
|
|
||||||
task.description.toLowerCase().includes(query)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return filtered;
|
|
||||||
});
|
|
||||||
/** Tasks grouped by status for display in columns */
|
|
||||||
const tasksByStatus = S(() => {
|
|
||||||
const filtered = filteredTasks.get();
|
|
||||||
return {
|
|
||||||
[STATUSES.TODO]: filtered.filter(t => t.status === STATUSES.TODO),
|
|
||||||
[STATUSES.IN_PROGRESS]: filtered.filter(t => t.status === STATUSES.IN_PROGRESS),
|
|
||||||
[STATUSES.DONE]: filtered.filter(t => t.status === STATUSES.DONE)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// <Add> signals and handlers for adding new tasks
|
|
||||||
const newTask = { title: '', description: '', priority: 'medium' };
|
|
||||||
const onAddTask = e => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!newTask.title) return;
|
|
||||||
|
|
||||||
S.action(tasks, "add", {
|
|
||||||
id: Date.now(),
|
|
||||||
status: STATUSES.TODO,
|
|
||||||
...newTask
|
|
||||||
});
|
|
||||||
e.target.reset();
|
|
||||||
};
|
|
||||||
// </Add>
|
|
||||||
const onCardEdit= on("card:edit", /** @param {CardEditEvent} ev */({ detail: [ id, task ] })=>
|
|
||||||
S.action(tasks, "update", id, task));
|
|
||||||
const onCardDelete= on("card:delete", /** @param {CardDeleteEvent} ev */({ detail: id })=>
|
|
||||||
S.action(tasks, "remove", id));
|
|
||||||
|
|
||||||
const { onDragable, onDragArea }= moveElementAddon(
|
|
||||||
(id, status) => S.action(tasks, "update", id, { status })
|
|
||||||
);
|
|
||||||
|
|
||||||
// Build the task manager UI
|
|
||||||
return el("div", { className: "task-manager" }).append(
|
|
||||||
el("header", { className: "app-header" }).append(
|
|
||||||
el("h1", "DDE Task Manager"),
|
|
||||||
el("div", { className: "app-controls" }).append(
|
|
||||||
el("input", {
|
|
||||||
type: "text",
|
|
||||||
placeholder: "Search tasks...",
|
|
||||||
value: searchQuery.get()
|
|
||||||
}, on("input", e => searchQuery.set(e.target.value))),
|
|
||||||
el("select", null,
|
|
||||||
on.defer(el=> el.value= filterPriority.get()),
|
|
||||||
on("change", e => filterPriority.set(e.target.value))
|
|
||||||
).append(
|
|
||||||
el("option", { value: "all", textContent: "All Priorities" }),
|
|
||||||
el("option", { value: "low", textContent: "Low Priority" }),
|
|
||||||
el("option", { value: "medium", textContent: "Medium Priority" }),
|
|
||||||
el("option", { value: "high", textContent: "High Priority" })
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
// Add new task form
|
|
||||||
el("form", { className: "new-task-form" }, on("submit", onAddTask)).append(
|
|
||||||
el("div", { className: "form-row" }).append(
|
|
||||||
el("input", {
|
|
||||||
type: "text",
|
|
||||||
placeholder: "New task title",
|
|
||||||
value: newTask.title,
|
|
||||||
required: true
|
|
||||||
}, on("input", e => newTask.title= e.target.value.trim())),
|
|
||||||
el("select", null,
|
|
||||||
on.defer(el=> el.value= newTask.priority),
|
|
||||||
on("change", e => newTask.priority= e.target.value)
|
|
||||||
).append(
|
|
||||||
el("option", { value: "low", textContent: "Low" }),
|
|
||||||
el("option", { value: "medium", textContent: "Medium" }),
|
|
||||||
el("option", { value: "high", textContent: "High" })
|
|
||||||
),
|
|
||||||
el("button", { type: "submit", className: "add-btn" }).append("Add Task")
|
|
||||||
),
|
|
||||||
el("textarea", {
|
|
||||||
placeholder: "Task description (optional)",
|
|
||||||
value: newTask.description
|
|
||||||
}, on("input", e => newTask.description= e.target.value.trim()))
|
|
||||||
),
|
|
||||||
|
|
||||||
// Task board with columns
|
|
||||||
el("div", { className: "task-board" }).append(
|
|
||||||
// Todo column
|
|
||||||
el("div", {
|
|
||||||
id: `column-${STATUSES.TODO}`,
|
|
||||||
className: "task-column"
|
|
||||||
}, onDragArea(STATUSES.TODO)).append(
|
|
||||||
el("h2", { className: "column-header" }).append(
|
|
||||||
"To Do ",
|
|
||||||
el("span", {
|
|
||||||
textContent: S(() => tasksByStatus.get()[STATUSES.TODO].length),
|
|
||||||
className: "task-count"
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
S.el(S(() => tasksByStatus.get()[STATUSES.TODO]), tasks =>
|
|
||||||
el("div", { className: "column-tasks" }).append(
|
|
||||||
...tasks.map(task=> el(TaskCard, { task, onDragable }, onCardEdit, onCardDelete))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
// In Progress column
|
|
||||||
el("div", {
|
|
||||||
id: `column-${STATUSES.IN_PROGRESS}`,
|
|
||||||
className: "task-column"
|
|
||||||
}, onDragArea(STATUSES.IN_PROGRESS)).append(
|
|
||||||
el("h2", { className: "column-header" }).append(
|
|
||||||
"In Progress ",
|
|
||||||
el("span", {
|
|
||||||
textContent: S(() => tasksByStatus.get()[STATUSES.IN_PROGRESS].length),
|
|
||||||
className: "task-count",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
S.el(S(() => tasksByStatus.get()[STATUSES.IN_PROGRESS]), tasks =>
|
|
||||||
el("div", { className: "column-tasks" }).append(
|
|
||||||
...tasks.map(task=> el(TaskCard, { task, onDragable }, onCardEdit, onCardDelete))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
// Done column
|
|
||||||
el("div", {
|
|
||||||
id: `column-${STATUSES.DONE}`,
|
|
||||||
className: "task-column"
|
|
||||||
}, onDragArea(STATUSES.DONE)).append(
|
|
||||||
el("h2", { className: "column-header" }).append(
|
|
||||||
"Done ",
|
|
||||||
el("span", {
|
|
||||||
textContent: S(() => tasksByStatus.get()[STATUSES.DONE].length),
|
|
||||||
className: "task-count",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
S.el(S(() => tasksByStatus.get()[STATUSES.DONE]), tasks =>
|
|
||||||
el("div", { className: "column-tasks" }).append(
|
|
||||||
...tasks.map(task=> el(TaskCard, { task, onDragable }, onCardEdit, onCardDelete))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/** @typedef {CustomEvent<[ string, Task ]>} CardEditEvent */
|
|
||||||
/** @typedef {CustomEvent<string>} CardDeleteEvent */
|
|
||||||
/**
|
|
||||||
* Task Card Component
|
|
||||||
* @type {(props: { task: Task, onDragable: (id: number) => ddeElementAddon<HTMLDivElement> }) => HTMLElement}
|
|
||||||
* @fires {CardEditEvent} card:edit
|
|
||||||
* @fires {CardDeleteEvent} card:delete
|
|
||||||
* */
|
|
||||||
function TaskCard({ task, onDragable }){
|
|
||||||
const { host }= scope;
|
|
||||||
const isEditing = S(false);
|
|
||||||
const onEditStart = () => isEditing.set(true);
|
|
||||||
|
|
||||||
const dispatchEdit= dispatchEvent("card:edit", host);
|
|
||||||
const dispatchDelete= dispatchEvent("card:delete", host).bind(null, task.id);
|
|
||||||
|
|
||||||
return el("div", {
|
|
||||||
id: `task-${task.id}`,
|
|
||||||
className: `task-card priority-${task.priority}`,
|
|
||||||
draggable: true
|
|
||||||
}, onDragable(task.id)).append(
|
|
||||||
S.el(isEditing, editing => editing
|
|
||||||
? el(EditMode)
|
|
||||||
: el().append(
|
|
||||||
el("div", { className: "task-header" }).append(
|
|
||||||
el("h3", { className: "task-title", textContent: task.title }),
|
|
||||||
el("div", { className: "task-actions" }).append(
|
|
||||||
el("button", {
|
|
||||||
textContent: "✎",
|
|
||||||
className: "edit-btn",
|
|
||||||
ariaLabel: "Edit task"
|
|
||||||
}, on("click", onEditStart)),
|
|
||||||
el("button", {
|
|
||||||
textContent: "✕",
|
|
||||||
className: "delete-btn",
|
|
||||||
ariaLabel: "Delete task"
|
|
||||||
}, on("click", dispatchDelete))
|
|
||||||
)
|
|
||||||
),
|
|
||||||
!task.description
|
|
||||||
? el()
|
|
||||||
: el("p", { className: "task-description", textContent: task.description }),
|
|
||||||
el("div", { className: "task-meta" }).append(
|
|
||||||
el("span", {
|
|
||||||
className: `priority-badge priority-${task.priority}`,
|
|
||||||
textContent: task.priority.charAt(0).toUpperCase() + task.priority.slice(1)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
function EditMode(){
|
|
||||||
const onSubmit = on("submit", e => {
|
|
||||||
e.preventDefault();
|
|
||||||
const formData = new FormData(/** @type {HTMLFormElement} */(e.target));
|
|
||||||
const title = formData.get("title");
|
|
||||||
const description = formData.get("description");
|
|
||||||
const priority = formData.get("priority");
|
|
||||||
isEditing.set(false);
|
|
||||||
dispatchEdit([ task.id, { title, description, priority } ]);
|
|
||||||
})
|
|
||||||
const onEditCancel = () => isEditing.set(false);
|
|
||||||
|
|
||||||
return el("form", { className: "task-edit-form" }, onSubmit).append(
|
|
||||||
el("input", {
|
|
||||||
name: "title",
|
|
||||||
className: "task-title-input",
|
|
||||||
defaultValue: task.title,
|
|
||||||
placeholder: "Task title",
|
|
||||||
required: true,
|
|
||||||
autoFocus: true
|
|
||||||
}),
|
|
||||||
el("textarea", {
|
|
||||||
name: "description",
|
|
||||||
className: "task-desc-input",
|
|
||||||
defaultValue: task.description,
|
|
||||||
placeholder: "Description (optional)"
|
|
||||||
}),
|
|
||||||
el("select", {
|
|
||||||
name: "priority",
|
|
||||||
}, on.defer(el=> el.value = task.priority)).append(
|
|
||||||
el("option", { value: "low", textContent: "Low Priority" }),
|
|
||||||
el("option", { value: "medium", textContent: "Medium Priority" }),
|
|
||||||
el("option", { value: "high", textContent: "High Priority" })
|
|
||||||
),
|
|
||||||
el("div", { className: "task-edit-actions" }).append(
|
|
||||||
el("button", {
|
|
||||||
textContent: "Cancel",
|
|
||||||
type: "button",
|
|
||||||
className: "cancel-btn"
|
|
||||||
}, on("click", onEditCancel)),
|
|
||||||
el("button", {
|
|
||||||
textContent: "Save",
|
|
||||||
type: "submit",
|
|
||||||
className: "save-btn"
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to handle move an element
|
|
||||||
* @param {(id: string, status: string) => void} onMoved
|
|
||||||
* */
|
|
||||||
function moveElementAddon(onMoved){
|
|
||||||
let draggedTaskId = null;
|
|
||||||
function onDragable(id) {
|
|
||||||
return element => {
|
|
||||||
on("dragstart", e => {
|
|
||||||
draggedTaskId= id;
|
|
||||||
e.dataTransfer.effectAllowed = 'move';
|
|
||||||
|
|
||||||
// Add some styling to the element being dragged
|
|
||||||
setTimeout(() => {
|
|
||||||
const el = document.getElementById(`task-${id}`);
|
|
||||||
if (el) el.classList.add('dragging');
|
|
||||||
}, 0);
|
|
||||||
})(element);
|
|
||||||
|
|
||||||
on("dragend", () => {
|
|
||||||
draggedTaskId= null;
|
|
||||||
|
|
||||||
// Remove the styling
|
|
||||||
const el = document.getElementById(`task-${id}`);
|
|
||||||
if (el) el.classList.remove('dragging');
|
|
||||||
})(element);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function onDragArea(status) {
|
|
||||||
return element => {
|
|
||||||
on("dragover", e => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.dataTransfer.dropEffect = 'move';
|
|
||||||
|
|
||||||
// Add a visual indicator for the drop target
|
|
||||||
const column = document.getElementById(`column-${status}`);
|
|
||||||
if (column) column.classList.add('drag-over');
|
|
||||||
})(element);
|
|
||||||
|
|
||||||
on("dragleave", () => {
|
|
||||||
// Remove the visual indicator
|
|
||||||
const column = document.getElementById(`column-${status}`);
|
|
||||||
if (column) column.classList.remove('drag-over');
|
|
||||||
})(element);
|
|
||||||
|
|
||||||
on("drop", e => {
|
|
||||||
e.preventDefault();
|
|
||||||
const id = draggedTaskId;
|
|
||||||
if (id) onMoved(id, status);
|
|
||||||
// Remove the visual indicator
|
|
||||||
const column = document.getElementById(`column-${status}`);
|
|
||||||
if (column) column.classList.remove('drag-over');
|
|
||||||
})(element);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return { onDragable, onDragArea };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render the component
|
|
||||||
document.body.append(
|
|
||||||
el("div", { style: "padding: 20px; background: #f5f5f5; min-height: 100vh;" }).append(
|
|
||||||
el(TaskManager)
|
|
||||||
),
|
|
||||||
el("style", `
|
|
||||||
.task-manager {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 1rem;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-header h1 {
|
|
||||||
margin: 0;
|
|
||||||
color: #2d3748;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-controls {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-controls input,
|
|
||||||
.app-controls select {
|
|
||||||
padding: 0.5rem;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new-task-form {
|
|
||||||
background: white;
|
|
||||||
padding: 1.5rem;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row input {
|
|
||||||
flex-grow: 1;
|
|
||||||
padding: 0.75rem;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row select {
|
|
||||||
width: 100px;
|
|
||||||
padding: 0.75rem;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-btn {
|
|
||||||
background: #4a90e2;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 0.75rem 1.5rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-btn:hover {
|
|
||||||
background: #3a7bc8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new-task-form textarea {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.75rem;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 1rem;
|
|
||||||
resize: vertical;
|
|
||||||
min-height: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-board {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(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,8 +8,7 @@ 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,6 +2,7 @@
|
|||||||
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 `S.observedAttributes`)
|
// listen to attribute changes (see `observedAttributes`)
|
||||||
}
|
}
|
||||||
disconnectedCallback(){
|
disconnectedCallback(){
|
||||||
// nice place to clean up
|
// nice place to clean up
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
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";
|
||||||
@ -8,6 +9,7 @@ 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,6 +1,5 @@
|
|||||||
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)=> {
|
||||||
@ -14,54 +13,3 @@ 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" component="<component-name>">-->
|
<!--<dde:mark type=\"reactive\" source=\"...\">-->
|
||||||
<!-- 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,11 +24,7 @@ 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.append(button);
|
document.body.appendChild(button);
|
||||||
document.body.append(button2);
|
document.body.appendChild(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.append(h1);
|
div.appendChild(h1);
|
||||||
|
|
||||||
const p = document.createElement('p');
|
const p = document.createElement('p');
|
||||||
p.textContent = 'Paragraph';
|
p.textContent = 'Paragraph';
|
||||||
div.append(p);
|
div.appendChild(p);
|
||||||
|
|
||||||
// append doesn't return parent
|
// appendChild doesn't return parent
|
||||||
// so chaining is not possible
|
// so chaining is not possible
|
||||||
|
|
||||||
// Add to DOM
|
// Add to DOM
|
||||||
document.body.append(div);
|
document.body.appendChild(div);
|
@ -1,19 +0,0 @@
|
|||||||
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,28 +1,15 @@
|
|||||||
import { el, on } from "deka-dom-el";
|
import { el, on } from "deka-dom-el";
|
||||||
function allLifecycleEvents(){
|
const paragraph= el("p", "See live-cycle events in console.",
|
||||||
return el("form", null,
|
|
||||||
el=> log({ type: "dde:created", detail: el }),
|
el=> log({ type: "dde:created", detail: el }),
|
||||||
on.connected(log),
|
on.connected(log),
|
||||||
on.disconnected(log),
|
on.disconnected(log),
|
||||||
).append(
|
on.attributeChanged(log));
|
||||||
el("select", { id: "country" }, on.defer(select => {
|
|
||||||
// This runs when the select is ready with all its options
|
|
||||||
select.value = "cz"; // Pre-select Czechia
|
|
||||||
log({ type: "dde:on.defer", detail: select });
|
|
||||||
})).append(
|
|
||||||
el("option", { value: "au", textContent: "Australia" }),
|
|
||||||
el("option", { value: "ca", textContent: "Canada" }),
|
|
||||||
el("option", { value: "cz", textContent: "Czechia" }),
|
|
||||||
),
|
|
||||||
el("p", "See lifecycle events in console."),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.body.append(
|
document.body.append(
|
||||||
el(allLifecycleEvents),
|
paragraph,
|
||||||
el("button", "Remove Element", on("click", function(){
|
el("button", "Update attribute", on("click", ()=> paragraph.setAttribute("test", Math.random().toString()))),
|
||||||
this.previousSibling.remove();
|
" ",
|
||||||
}))
|
el("button", "Remove", on("click", ()=> paragraph.remove()))
|
||||||
);
|
);
|
||||||
|
|
||||||
/** @param {Partial<CustomEvent>} event */
|
/** @param {Partial<CustomEvent>} event */
|
||||||
|
@ -8,6 +8,7 @@ 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 } from "deka-dom-el";
|
import { el, on } 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.get().repeat(clicks.get())}`),
|
textContent: S(() => `Hello World ${emoji().repeat(clicks())}`),
|
||||||
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.set(clicks.get() + 1)),
|
on("click", ()=> clicks(clicks() + 1)),
|
||||||
on("keyup", ()=> clicks.set(clicks.get() - 2)),
|
on("keyup", ()=> clicks(clicks() - 2)),
|
||||||
),
|
),
|
||||||
el("select", null, onChange).append(
|
el("select", null, onChange).append(
|
||||||
el(OptionComponent, "🎉", isSelected),//OR { textContent: "🎉" }
|
el(OptionComponent, "🎉", isSelected),//OR { textContent: "🎉" }
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
// 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";
|
|
@ -1,115 +0,0 @@
|
|||||||
// 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));
|
|
@ -1,381 +0,0 @@
|
|||||||
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.set("Text changed! "+(new Date()).toString())
|
textContent("Text changed! "+(new Date()).toString())
|
||||||
});
|
});
|
||||||
return el("p", textContent, onclickChange);
|
return el("p", textContent, onclickChange);
|
||||||
}
|
}
|
||||||
|
14
docs/components/examples/scopes/custom-scope.js
Normal file
14
docs/components/examples/scopes/custom-scope.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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,9 +16,7 @@ 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!!!
|
||||||
S.on(count, (count)=>
|
host().querySelector("button").disabled = count.get() >= 10;
|
||||||
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 `S.clear(todos)`
|
[S.symbols.onclear](){ // this covers `O.clear(todos)`
|
||||||
S.clear(...this.value);
|
S.clear(...this.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// 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() {
|
||||||
@ -7,27 +8,6 @@ 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("-");
|
||||||
@ -45,3 +25,19 @@ function AsyncComponent() {
|
|||||||
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,7 +11,6 @@ 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,7 +17,6 @@ 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";
|
import { register, unregister, queue } from "deka-dom-el/jsdom.js";
|
||||||
|
|
||||||
// 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/docs", { recursive: true });
|
mkdirSync("./dist", { recursive: true });
|
||||||
|
|
||||||
// Build each page
|
// Build each page
|
||||||
for (const page of pages) {
|
for (const page of pages) {
|
||||||
@ -23,7 +23,6 @@ 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
|
||||||
@ -36,7 +35,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/docs/${page.id}.html`, html);
|
writeFileSync(`./dist/${page.id}.html`, html);
|
||||||
|
|
||||||
console.log(`Built page: ${page.id}.html`);
|
console.log(`Built page: ${page.id}.html`);
|
||||||
}
|
}
|
||||||
|
@ -1,83 +0,0 @@
|
|||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,92 +0,0 @@
|
|||||||
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,33 +1,3 @@
|
|||||||
import { styles } from "../ssr.js";
|
|
||||||
styles.css`
|
|
||||||
[data-dde-mark] {
|
|
||||||
opacity: .5;
|
|
||||||
filter: grayscale();
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
|
||||||
animation: fadein 2s infinite ease forwards;;
|
|
||||||
}
|
|
||||||
|
|
||||||
position: relative;
|
|
||||||
&::after {
|
|
||||||
content: "Loading Ireland…";
|
|
||||||
background-color: rgba(0, 0, 0, .5);
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 5px 10px;
|
|
||||||
position: absolute;
|
|
||||||
top: 3%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes fadein {
|
|
||||||
from { opacity: .5; }
|
|
||||||
to { opacity: .85; }
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
import { el, queue } from "deka-dom-el";
|
import { el, queue } from "deka-dom-el";
|
||||||
import { addEventListener, registerClientFile } from "../ssr.js";
|
import { addEventListener, registerClientFile } from "../ssr.js";
|
||||||
import { relative } from "node:path";
|
import { relative } from "node:path";
|
||||||
@ -43,24 +13,18 @@ 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 = {} }) {
|
||||||
// relative src against the current directory
|
// relative src against the current directory
|
||||||
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: "ireland", name: ireland.name });
|
||||||
queue(
|
queue(import(path).then(module => {
|
||||||
import(path)
|
|
||||||
.then(module => {
|
|
||||||
const component = module[exportName];
|
const component = module[exportName];
|
||||||
const content= el(component, props, mark(id));
|
element.replaceWith(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,6 +11,10 @@ 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 (accepts string, number or signal)",
|
" — simple element containing only text",
|
||||||
),
|
),
|
||||||
el("li").append(
|
el("li").append(
|
||||||
el("code", "el(<tag-name>, <object>)[.append(...)]: <element-from-tag-name>"),
|
el("code", "el(<tag-name>, <object>)[.append(...)]: <element-from-tag-name>"),
|
||||||
@ -26,6 +26,6 @@ export function mnemonic(){
|
|||||||
el("li").append(
|
el("li").append(
|
||||||
el("code", "elNS(<namespace>)(<as-el-see-above>)[.append(...)]: <element-based-on-arguments>"),
|
el("code", "elNS(<namespace>)(<as-el-see-above>)[.append(...)]: <element-based-on-arguments>"),
|
||||||
" — typically SVG elements",
|
" — typically SVG elements",
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -12,24 +12,19 @@ export function mnemonic(){
|
|||||||
" — corresponds to custom elemnets callbacks ", el("code", "<live-cycle>Callback(...){...}"),
|
" — corresponds to custom elemnets callbacks ", el("code", "<live-cycle>Callback(...){...}"),
|
||||||
". To connect to custom element see following page, else it is simulated by MutationObserver."
|
". To connect to custom element see following page, else it is simulated by MutationObserver."
|
||||||
),
|
),
|
||||||
el("li").append(
|
|
||||||
el("code", "on.defer(<identity>=> <identity>)(<identity>)"),
|
|
||||||
" — calls callback later",
|
|
||||||
),
|
|
||||||
el("li").append(
|
el("li").append(
|
||||||
el("code", "dispatchEvent(<event>[, <options>])(element)"),
|
el("code", "dispatchEvent(<event>[, <options>])(element)"),
|
||||||
" — just ", el("code", "<element>.dispatchEvent(new Event(<event>[, <options>]))")
|
" — just ", el("code", "<element>.dispatchEvent(new Event(<event>[, <options>]))")
|
||||||
),
|
),
|
||||||
|
el("li").append(
|
||||||
|
el("code", "dispatchEvent(<event>, <element>)([<detail>])"),
|
||||||
|
" — just ", el("code", "<element>.dispatchEvent(new Event(<event>))"), " or ",
|
||||||
|
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)"
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
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,10 +14,6 @@ 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,6 +14,10 @@ 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",
|
||||||
@ -25,11 +29,6 @@ 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)",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
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: "*",
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
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,6 +16,7 @@ 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;
|
||||||
|
|
||||||
@ -72,7 +73,6 @@ 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 var(--primary-light);
|
outline: 3px solid hsl(231, 48%, 70%);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,34 +193,6 @@ 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 {
|
||||||
@ -243,8 +215,8 @@ body {
|
|||||||
}
|
}
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
body {
|
body {
|
||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: var(--header-height) 1fr;
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: var(--sidebar-width) 1fr;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"header header"
|
"header header"
|
||||||
"sidebar content";
|
"sidebar content";
|
||||||
@ -262,7 +234,7 @@ body > main {
|
|||||||
}
|
}
|
||||||
body > main > *, body > main slot > * {
|
body > main > *, body > main slot > * {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: calc(var(--body-max-width) * 5/3);
|
max-width: 100%;
|
||||||
margin-inline: auto;
|
margin-inline: auto;
|
||||||
grid-column: main;
|
grid-column: main;
|
||||||
}
|
}
|
||||||
@ -278,7 +250,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: 1rem;
|
scroll-margin-top: calc(var(--header-height) + 1rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make clickable heading links for better navigation */
|
/* Make clickable heading links for better navigation */
|
||||||
@ -295,8 +267,9 @@ 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)):not(:has( .tabs)) {
|
.illustration:not(:has( .comparison)){
|
||||||
grid-column: main;
|
grid-column: main;
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
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`Reactive DOM library for creating dynamic UIs with a declarative syntax`,
|
description: t`A lightweight, reactive DOM library for creating dynamic UIs with a declarative syntax`,
|
||||||
};
|
};
|
||||||
|
|
||||||
import { el } from "deka-dom-el";
|
import { el } from "deka-dom-el";
|
||||||
@ -12,14 +11,9 @@ 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",
|
||||||
@ -31,27 +25,28 @@ const references= {
|
|||||||
};
|
};
|
||||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||||
export function page({ pkg, info }){
|
export function page({ pkg, info }){
|
||||||
|
const page_id= info.id;
|
||||||
return el(simplePage, { info, pkg }).append(
|
return el(simplePage, { info, pkg }).append(
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Welcome to Deka DOM Elements (dd<el> or DDE) — a library for building dynamic UIs with
|
Welcome to Deka DOM Elements (DDE) — a lightweight library for building dynamic UIs with a declarative
|
||||||
a declarative syntax that stays close to the native DOM API. dd<el> gives you powerful reactive tools
|
syntax that stays close to the native DOM API. DDE gives you powerful reactive tools without the complexity
|
||||||
without the complexity and overhead of larger frameworks.
|
and overhead of larger frameworks.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "callout" }).append(
|
el("div", { className: "callout" }).append(
|
||||||
el("h4", t`Key Benefits of dd<el>`),
|
el("h4", t`What Makes DDE Special`),
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
el("li", t`No build step required — use directly in the browser`),
|
el("li", t`No build step required — use directly in the browser`),
|
||||||
el("li", t`Lightweight core (~10–15kB minified) without unnecessary dependencies (0 at now 😇)`),
|
el("li", t`Lightweight core (~10–15kB minified) with zero dependencies`),
|
||||||
el("li", t`Natural DOM API — work with real DOM nodes, not abstractions`),
|
el("li", t`Natural DOM API — work with real DOM nodes, not abstractions`),
|
||||||
el("li", t`Built-in (but optional) reactivity with simplified but powerful signals system`),
|
el("li", t`Built-in reactivity with powerful signals system`),
|
||||||
el("li", t`Clean code organization with the 3PS pattern`)
|
el("li", t`Clean code organization with the 3PS pattern`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/introducing/helloWorld.js") }),
|
el(example, { src: fileURL("./components/examples/introducing/helloWorld.js"), page_id }),
|
||||||
|
|
||||||
el(h3, { textContent: t`The 3PS Pattern: Simplified architecture pattern`, id: "h-3ps" }),
|
el(h3, { textContent: t`The 3PS Pattern: A Better Way to Build UIs`, id: "h-3ps" }),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
At the heart of dd<el> is the 3PS (3-Part Separation) pattern. This simple yet powerful approach helps you
|
At the heart of DDE 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.
|
||||||
`),
|
`),
|
||||||
@ -59,116 +54,62 @@ export function page({ pkg, info }){
|
|||||||
el("div", { className: "tabs" }).append(
|
el("div", { className: "tabs" }).append(
|
||||||
el("div", { className: "tab" }).append(
|
el("div", { className: "tab" }).append(
|
||||||
el("h5", t`Traditional DOM Manipulation`),
|
el("h5", t`Traditional DOM Manipulation`),
|
||||||
el(code, { src: fileURL("./components/examples/introducing/3ps-before.js") }),
|
el(code, { src: fileURL("./components/examples/introducing/3ps-before.js"), page_id }),
|
||||||
),
|
),
|
||||||
el("div", { className: "tab" }).append(
|
el("div", { className: "tab" }).append(
|
||||||
el("h5", t`dd<el>'s 3PS Pattern`),
|
el("h5", t`DDE's 3PS Pattern`),
|
||||||
el(code, { src: fileURL("./components/examples/introducing/3ps.js") }),
|
el(code, { src: fileURL("./components/examples/introducing/3ps.js"), page_id }),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
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", "React to Changes")}: Define how UI elements and other parts of your app react to state
|
${el("strong", "Bind to Elements")}: Define how UI elements react to state changes
|
||||||
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 ${el("strong", "is not something new and/or special to dd<el>")}. It’s based on ${el("a", {
|
approach shares principles with more formal patterns like ${el("a", { textContent: "MVVM",
|
||||||
textContent: "MVC", ...references.w_mvc })} (${el("a", { textContent: "MVVM", ...references.w_mvv })}),
|
...references.w_mvv })} and ${el("a", { textContent: "MVC", ...references.w_mvc })}, but with less
|
||||||
but is there presented in simpler form.
|
overhead and complexity.
|
||||||
`),
|
`),
|
||||||
|
|
||||||
el("div", { className: "note" }).append(
|
el("div", { className: "note" }).append(
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
The 3PS pattern becomes especially powerful when combined with components, allowing you to create
|
The 3PS pattern becomes especially powerful when combined with components, allowing you to create
|
||||||
reusable pieces of UI with encapsulated state and behavior. You’ll learn more about this in the
|
reusable pieces of UI with encapsulated state and behavior. You'll learn more about this in the
|
||||||
following sections.
|
following sections.
|
||||||
`),
|
|
||||||
el("p").append(T`
|
|
||||||
The 3PS pattern isn’t required to use with dd<el> but it is good practice to follow it or some similar
|
|
||||||
software architecture.
|
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`Getting Started`),
|
|
||||||
el("p").append(T`
|
|
||||||
There are multiple ways to include dd<el> in your project. You can use npm for a full development setup,
|
|
||||||
or directly include it from a CDN for quick prototyping.
|
|
||||||
`),
|
|
||||||
el("h4", "npm installation"),
|
|
||||||
el(code, { content: "npm install deka-dom-el --save", language: "shell" }),
|
|
||||||
el("p").append(T`
|
|
||||||
…see ${el("a", { textContent: "package page", ...references.npm, target: "_blank" })}.
|
|
||||||
`),
|
|
||||||
|
|
||||||
el("h4", "CDN / Direct Script Usage"),
|
|
||||||
el("p").append(T`
|
|
||||||
Use the interactive selector below to choose your preferred format:
|
|
||||||
`),
|
|
||||||
el(getLibraryUrl),
|
|
||||||
el("div", { className: "note" }).append(
|
|
||||||
el("p").append(T`
|
|
||||||
Based on your selection, you can use dd<el> in your project like this:
|
|
||||||
`),
|
|
||||||
el(code, { content: `
|
|
||||||
// ESM format (modern JavaScript with import/export)
|
|
||||||
import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.min.js";
|
|
||||||
|
|
||||||
// Or with IIFE format (creates a global DDE object)
|
|
||||||
// <script src="https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/iife-with-signals.min.js"></script>
|
|
||||||
const { el, on } = DDE;
|
|
||||||
`, language: "js" }),
|
|
||||||
),
|
|
||||||
|
|
||||||
el(h3, t`How to Use This Documentation`),
|
el(h3, t`How to Use This Documentation`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
This guide will take you through dd<el>’s features step by step:
|
This guide will take you through DDE's features step by step:
|
||||||
`),
|
`),
|
||||||
el("ol", { start: 2 }).append(
|
el("ol").append(
|
||||||
el("li").append(T`${el("a", { href: "p02-elements.html" }).append(el("strong", "Elements"))} — Creating
|
el("li").append(...T`${el("strong", "Elements")} — Creating and manipulating DOM elements`),
|
||||||
and manipulating DOM elements`),
|
el("li").append(...T`${el("strong", "Events")} — Handling user interactions and lifecycle events`),
|
||||||
el("li").append(T`${el("a", { href: "p03-events.html" }).append(el("strong", "Events and Addons"))} —
|
el("li").append(...T`${el("strong", "Signals")} — Adding reactivity to your UI`),
|
||||||
Handling user interactions and lifecycle events`),
|
el("li").append(...T`${el("strong", "Scopes")} — Managing component lifecycles`),
|
||||||
el("li").append(T`${el("a", { href: "p04-signals.html" }).append(el("strong", "Signals"))} — Adding
|
el("li").append(...T`${el("strong", "Custom Elements")} — Building web components`),
|
||||||
reactivity to your UI`),
|
el("li").append(...T`${el("strong", "Debugging")} — Tools to help you build and fix your apps`),
|
||||||
el("li").append(T`${el("a", { href: "p05-scopes.html" }).append(el("strong", "Scopes"))} — Managing
|
el("li").append(...T`${el("strong", "Extensions")} — Integrating third-party functionalities`),
|
||||||
component lifecycles`),
|
el("li").append(...T`${el("strong", "Ireland Components")} — Creating interactive demos with server-side pre-rendering`),
|
||||||
el("li").append(T`${el("a", { href: "p06-customElement.html" }).append(el("strong", "Web Components"))} —
|
el("li").append(...T`${el("strong", "SSR")} — Server-side rendering with DDE`)
|
||||||
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`
|
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,8 +105,13 @@ ${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-flow: row wrap;
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
border-right: none;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +121,14 @@ ${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;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ 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 }){
|
||||||
@ -27,9 +26,6 @@ export function simplePage({ pkg, info }){
|
|||||||
|
|
||||||
// Navigation between pages
|
// Navigation between pages
|
||||||
el(prevNext, info)
|
el(prevNext, info)
|
||||||
),
|
)
|
||||||
|
|
||||||
// Scroll to top button
|
|
||||||
el(scrollTop),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -47,84 +47,81 @@ const references= {
|
|||||||
};
|
};
|
||||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||||
export function page({ pkg, info }){
|
export function page({ pkg, info }){
|
||||||
|
const page_id= info.id;
|
||||||
return el(simplePage, { info, pkg }).append(
|
return el(simplePage, { info, pkg }).append(
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Building user interfaces in JavaScript often involves creating and manipulating DOM elements.
|
Building user interfaces in JavaScript often involves creating and manipulating DOM elements.
|
||||||
dd<el> provides a simple yet powerful approach to element creation that is declarative, chainable,
|
DDE provides a simple yet powerful approach to element creation that is declarative, chainable,
|
||||||
and maintains a clean syntax close to HTML structure.
|
and maintains a clean syntax close to HTML structure.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "callout" }).append(
|
el("div", { class: "callout" }).append(
|
||||||
el("h4", t`dd<el> Elements: Key Benefits`),
|
el("h4", t`DDE 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 declarative property/attribute handling across browsers`),
|
el("li", t`Normalized 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") }),
|
el(code, { src: fileURL("./components/examples/elements/intro.js"), page_id }),
|
||||||
|
|
||||||
el(h3, t`Creating Elements: Native vs dd<el>`),
|
el(h3, t`Creating Elements: Native vs DDE`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
In standard JavaScript, you create DOM elements using the
|
In standard JavaScript, you create DOM elements using the
|
||||||
${el("a", references.mdn_create).append(el("code", "document.createElement()"))} method
|
${el("a", references.mdn_create).append(el("code", "document.createElement()"))} method
|
||||||
and then set properties individually or with ${el("code", "Object.assign()")}:
|
and then set properties individually or with Object.assign():
|
||||||
`),
|
`),
|
||||||
el("div", { className: "illustration" }).append(
|
el("div", { class: "illustration" }).append(
|
||||||
el("div", { className: "comparison" }).append(
|
el("div", { class: "comparison" }).append(
|
||||||
el("div").append(
|
el("div").append(
|
||||||
el("h5", t`Native DOM API`),
|
el("h5", t`Native DOM API`),
|
||||||
el(code, { src: fileURL("./components/examples/elements/native-dom-create.js") })
|
el(code, { src: fileURL("./components/examples/elements/native-dom-create.js"), page_id })
|
||||||
),
|
),
|
||||||
el("div").append(
|
el("div").append(
|
||||||
el("h5", t`dd<el> Approach`),
|
el("h5", t`DDE Approach`),
|
||||||
el(code, { src: fileURL("./components/examples/elements/dde-dom-create.js") })
|
el(code, { src: fileURL("./components/examples/elements/dde-dom-create.js"), page_id })
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
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") }),
|
el(example, { src: fileURL("./components/examples/elements/dekaCreateElement.js"), page_id }),
|
||||||
|
|
||||||
el(h3, t`Advanced Property Assignment`),
|
el(h3, t`Advanced Property Assignment`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
The ${el("code", "assign")} function is the heart of dd<el>’s element property handling. It is internally
|
The ${el("code", "assign")} function is the heart of DDE's element property handling. It is internally used
|
||||||
used to assign properties using the ${el("code", "el")} function. ${el("code", "assign")} provides
|
to assign properties using the ${el("code", "el")} function. ${el("code", "assign")} provides intelligent
|
||||||
intelligent assignment of both ${el("a", { textContent: "properties (IDL)", ...references.mdn_idl })}
|
assignment of both properties (IDL) and attributes:
|
||||||
and attributes:
|
|
||||||
`),
|
`),
|
||||||
el("div", { className: "function-table" }).append(
|
el("div", { class: "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").append(T`Both ${el("code", "dataset.keyName")} and ${el("code", "dataKeyName")} syntaxes are
|
el("dd", t`Both dataset.* and data-* syntaxes supported (same for ARIA)`),
|
||||||
supported (same for ${el("code", "aria")}/${el("code", "ariaset")})`),
|
|
||||||
|
|
||||||
el("dt", t`Style Handling`),
|
el("dt", t`Style Handling`),
|
||||||
el("dd").append(T`Accepts string or object notation for ${el("code", "style")} property`),
|
el("dd", t`Accepts string or object notation for style property`),
|
||||||
|
|
||||||
el("dt", t`Class Management`),
|
el("dt", t`Class Management`),
|
||||||
el("dd").append(T`Works with ${el("code", "className")} (${el("code", "class")}) and ${el("code",
|
el("dd", t`Works with className, class, or classList object for toggling classes`),
|
||||||
"classList")} object for toggling classes`),
|
|
||||||
|
|
||||||
el("dt", t`Force Modes`),
|
el("dt", t`Force Modes`),
|
||||||
el("dd").append(T`Use ${el("code", "=")} prefix to force attribute mode, ${el("code", ".")} prefix to
|
el("dd", t`Use = prefix to force attribute mode, . prefix to force property mode`),
|
||||||
force property mode`),
|
|
||||||
|
|
||||||
el("dt", t`Attribute Removal`),
|
el("dt", t`Attribute Removal`),
|
||||||
el("dd").append(T`Pass ${el("code", "undefined")} to remove a property or attribute`)
|
el("dd", t`Pass undefined to remove a property or attribute`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/elements/dekaAssign.js") }),
|
el(example, { src: fileURL("./components/examples/elements/dekaAssign.js"), page_id }),
|
||||||
|
|
||||||
el("div", { className: "note" }).append(
|
el("div", { class: "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 })}.
|
||||||
@ -132,81 +129,75 @@ export function page({ pkg, info }){
|
|||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`Building DOM Trees with Chainable Methods`),
|
el(h3, t`Building DOM Trees with Chainable Methods`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
One of the most powerful features of dd<el> is its approach to building element trees.
|
One of the most powerful features of DDE is its approach to building element trees.
|
||||||
Unlike the native DOM API which doesn’t return the parent after ${el("code", "append()")}, dd<el>’s
|
Unlike the native DOM API which doesn't return the parent after appendChild(), DDE's
|
||||||
${el("code", "append()")} always returns the parent element:
|
append() always returns the parent element:
|
||||||
`),
|
`),
|
||||||
el("div", { className: "illustration" }).append(
|
el("div", { class: "illustration" }).append(
|
||||||
el("div", { className: "comparison" }).append(
|
el("div", { class: "comparison" }).append(
|
||||||
el("div", { className: "bad-practice" }).append(
|
el("div", { class: "bad-practice" }).append(
|
||||||
el("h5", t`❌ Native DOM API`),
|
el("h5", t`❌ Native DOM API`),
|
||||||
el(code, { src: fileURL("./components/examples/elements/native-dom-tree.js") })
|
el(code, { src: fileURL("./components/examples/elements/native-dom-tree.js"), page_id })
|
||||||
),
|
),
|
||||||
el("div", { className: "good-practice" }).append(
|
el("div", { class: "good-practice" }).append(
|
||||||
el("h5", t`✅ dd<el> Approach`),
|
el("h5", t`✅ DDE Approach`),
|
||||||
el(code, { src: fileURL("./components/examples/elements/dde-dom-tree.js") })
|
el(code, { src: fileURL("./components/examples/elements/dde-dom-tree.js"), page_id })
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
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") }),
|
el(example, { src: fileURL("./components/examples/elements/dekaAppend.js"), page_id }),
|
||||||
|
|
||||||
el(h3, t`Using Components to Build UI Fragments`),
|
el(h3, t`Using Components to Build UI Fragments`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
The ${el("code", "el")} function is overloaded to support both tag names and function components.
|
The ${el("code", "el")} function is overloaded to support both tag names and function components.
|
||||||
This lets you refactor complex UI trees into reusable pieces:
|
This lets you refactor complex UI trees into reusable pieces:
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/elements/dekaBasicComponent.js") }),
|
el(example, { src: fileURL("./components/examples/elements/dekaBasicComponent.js"), page_id }),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Component functions receive the properties object as their first argument, just like regular elements.
|
Component functions receive the properties object as their first argument, just like regular elements.
|
||||||
This makes it easy to pass data down to components and create reusable UI fragments.
|
This makes it easy to pass data down to components and create reusable UI fragments.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "tip" }).append(
|
el("div", { class: "tip" }).append(
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
It’s helpful to use naming conventions similar to native DOM elements for your components.
|
It's helpful to use naming conventions similar to native DOM elements for your components.
|
||||||
This allows you to keeps your code consistent with the DOM API.
|
This allows you to use ${el("a", { textContent: "destructuring assignment", ...references.mdn_destruct })}
|
||||||
`),
|
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, dd<el> provides the ${el("code", "elNS")}
|
For non-HTML elements like SVG, MathML, or custom namespaces, DDE provides the ${el("code", "elNS")} function
|
||||||
function which corresponds to the native ${el("a", references.mdn_ns).append(el("code",
|
which corresponds to the native ${el("a", references.mdn_ns).append(el("code", "document.createElementNS"))}:
|
||||||
"document.createElementNS"))}:
|
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/elements/dekaElNS.js") }),
|
el(example, { src: fileURL("./components/examples/elements/dekaElNS.js"), page_id }),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
This function returns a namespace-specific element creator, allowing you to work with any element type
|
This function returns a namespace-specific element creator, allowing you to work with any element type
|
||||||
using the same consistent interface.
|
using the same consistent interface.
|
||||||
`),
|
`),
|
||||||
|
|
||||||
el(h3, t`Best Practices for Declarative DOM Creation`),
|
el(h3, t`Best Practices for Declarative DOM Creation`),
|
||||||
el("ol").append(
|
el("ol").append(
|
||||||
el("li").append(T`
|
el("li").append(...T`
|
||||||
${el("strong", "Use component functions for reusable UI fragments:")} Extract common UI patterns
|
${el("strong", "Use component functions for reusable UI fragments:")} Extract common UI patterns
|
||||||
into reusable functions that return elements.
|
into reusable functions that return elements.
|
||||||
`),
|
`),
|
||||||
el("li").append(T`
|
el("li").append(...T`
|
||||||
${el("strong", "Leverage destructuring for cleaner component code:")} Use
|
${el("strong", "Leverage destructuring for cleaner component code:")} Use
|
||||||
${el("a", { textContent: "destructuring", ...references.mdn_destruct })} to extract properties
|
${el("a", { textContent: "destructuring", ...references.mdn_destruct })} to extract properties
|
||||||
from the props object for cleaner component code.
|
from the props object for cleaner component code.
|
||||||
`),
|
`),
|
||||||
el("li").append(T`
|
el("li").append(...T`
|
||||||
${el("strong", "Leverage chainable methods for better performance:")} Use chainable methods
|
${el("strong", "Leverage chainable methods for better performance:")} Use chainable methods like
|
||||||
${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,17 +36,23 @@ 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. dd<el> provides a clean, declarative approach to
|
Events are at the core of interactive web applications. DDE 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 dd<el>’s Event System and Addons Matters`),
|
el("h4", t`Why DDE'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`),
|
||||||
@ -56,76 +62,71 @@ export function page({ pkg, info }){
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(code, { src: fileURL("./components/examples/events/intro.js") }),
|
el(code, { src: fileURL("./components/examples/events/intro.js"), page_id }),
|
||||||
|
|
||||||
el(h3, t`Events and Listeners: Two Approaches`),
|
el(h3, t`Events and Listeners: Two Approaches`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
In JavaScript you can listen to native DOM events using
|
In JavaScript you can listen to native DOM events using
|
||||||
${el("a", references.mdn_listen).append(el("code", "element.addEventListener(type, listener, options)"))}.
|
${el("a", references.mdn_listen).append(el("code", "element.addEventListener(type, listener, options)"))}.
|
||||||
dd<el> provides an alternative approach with arguments ordered differently to better fit its declarative
|
DDE provides an alternative approach with arguments ordered differently to better fit its declarative style:
|
||||||
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);`, language: "js" })
|
el(code, { content: `element.addEventListener('click', callback, options);`, page_id })
|
||||||
),
|
),
|
||||||
el("div", { className: "tab" }).append(
|
el("div", { className: "tab" }).append(
|
||||||
el("h5", t`dd<el> Approach`),
|
el("h5", t`DDE Approach`),
|
||||||
el(code, { content: `on("click", callback, options)(element);`, language: "js" })
|
el(code, { content: `on('click', callback, options)(element);`, page_id })
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
The main benefit of dd<el>’s approach is that it works as an Addon (see below), making it easy to integrate
|
The main benefit of DDE's approach is that it works as an Addon, making it easy to integrate
|
||||||
directly into element declarations.
|
directly into element declarations.
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/events/compare.js") }),
|
el(example, { src: fileURL("./components/examples/events/compare.js"), page_id }),
|
||||||
|
|
||||||
el(h3, t`Removing Event Listeners`),
|
el(h3, t`Removing Event Listeners`),
|
||||||
el("div", { className: "note" }).append(
|
el("div", { className: "note" }).append(
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Unlike the native addEventListener/removeEventListener pattern, dd<el> uses ${el("strong", "only")}
|
Unlike the native addEventListener/removeEventListener pattern, DDE uses the ${el("a", {
|
||||||
${el("a", { textContent: "AbortSignal", ...references.mdn_abortListener })} for declarative removal:
|
textContent: "AbortSignal", ...references.mdn_abortListener })} for declarative approach for removal:
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/events/abortSignal.js") }),
|
el(example, { src: fileURL("./components/examples/events/abortSignal.js"), page_id }),
|
||||||
el("p").append(T`
|
|
||||||
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", dataTab: "html-attr" }).append(
|
el("div", { className: "tab", "data-tab": "html-attr" }).append(
|
||||||
el("h4", t`HTML Attribute Style`),
|
el("h4", t`HTML Attribute Style`),
|
||||||
el(code, { src: fileURL("./components/examples/events/attribute-event.js") }),
|
el(code, { src: fileURL("./components/examples/events/attribute-event.js"), page_id }),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Forces usage as an HTML attribute. Corresponds to
|
Forces usage as an HTML attribute. Corresponds to
|
||||||
${el("code", `<button onclick="console.log(event)">click me</button>`)}. This can be particularly
|
${el("code", `<button onclick="console.log(event)">click me</button>`)}. This can be particularly
|
||||||
useful for SSR scenarios.
|
useful for SSR scenarios.
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
el("div", { className: "tab", dataTab: "property" }).append(
|
el("div", { className: "tab", "data-tab": "property" }).append(
|
||||||
el("h4", t`Property Assignment`),
|
el("h4", t`Property Assignment`),
|
||||||
el(code, { src: fileURL("./components/examples/events/property-event.js") }),
|
el(code, { src: fileURL("./components/examples/events/property-event.js"), page_id }),
|
||||||
el("p", t`Assigns the event handler directly to the element’s property.`)
|
el("p", t`Assigns the event handler directly to the element's property.`)
|
||||||
),
|
),
|
||||||
el("div", { className: "tab", dataTab: "addon" }).append(
|
el("div", { className: "tab", "data-tab": "addon" }).append(
|
||||||
el("h4", t`Addon Approach`),
|
el("h4", t`Addon Approach`),
|
||||||
el(code, { src: fileURL("./components/examples/events/chain-event.js") }),
|
el(code, { src: fileURL("./components/examples/events/chain-event.js"), page_id }),
|
||||||
el("p", t`Uses the addon pattern (so adds the event listener to the element), see above.`)
|
el("p", t`Uses the addon pattern, see above.`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
For a deeper comparison of these approaches, see
|
For a deeper comparison of these approaches, see
|
||||||
${el("a", { textContent: "WebReflection’s detailed analysis", ...references.web_events })}.
|
${el("a", { textContent: "WebReflection's detailed analysis", ...references.web_events })}.
|
||||||
`),
|
`),
|
||||||
|
|
||||||
el(h3, t`Understanding Addons`),
|
el(h3, t`Understanding Addons`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Addons are a powerful pattern in dd<el> that extends beyond just event handling.
|
Addons are a powerful pattern in DDE 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(
|
||||||
@ -135,27 +136,26 @@ export function page({ pkg, info }){
|
|||||||
el("li", t`Set up lifecycle behaviors`),
|
el("li", t`Set up lifecycle behaviors`),
|
||||||
el("li", t`Integrate third-party libraries`),
|
el("li", t`Integrate third-party libraries`),
|
||||||
el("li", t`Create reusable element behaviors`),
|
el("li", t`Create reusable element behaviors`),
|
||||||
el("li", t`Capture element references`), // TODO: add example?
|
el("li", t`Capture element references`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
You can use Addons as ≥3rd argument of the ${el("code", "el")} function, making it possible to
|
You can use Addons as ≥3rd argument of the ${el("code", "el")} function, making it possible to
|
||||||
extend your templates with additional functionality:
|
extend your templates with additional functionality:
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/events/templateWithListeners.js") }),
|
el(example, { src: fileURL("./components/examples/events/templateWithListeners.js"), page_id }),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
As the example shows, you can provide types in JSDoc+TypeScript using the global type
|
As the example shows, you can provide types in JSDoc+TypeScript using the global type
|
||||||
${el("code", "ddeElementAddon")}. Notice how Addons can also be used to get element references.
|
${el("code", "ddeElementAddon")}. Notice how Addons can also be used to get element references.
|
||||||
`),
|
`),
|
||||||
|
|
||||||
el(h3, t`Lifecycle Events`),
|
el(h3, t`Lifecycle Events`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Addons are called immediately when an element is created, even before it’s connected to the live DOM.
|
Addons are called immediately when an element is created, even before it's connected to the live DOM.
|
||||||
You can think of an Addon as an “oncreate” event handler.
|
You can think of an Addon as an "oncreate" event handler.
|
||||||
`),
|
`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
dd<el> provides two additional lifecycle events that correspond to ${el("a", { textContent:
|
DDE provides three additional lifecycle events that correspond to custom element lifecycle callbacks:
|
||||||
"custom element", ...references.mdn_customElements })} lifecycle callbacks and component patterns:
|
|
||||||
`),
|
`),
|
||||||
el("div", { className: "function-table" }).append(
|
el("div", { className: "function-table" }).append(
|
||||||
el("dl").append(
|
el("dl").append(
|
||||||
@ -164,74 +164,61 @@ 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") }),
|
el(example, { src: fileURL("./components/examples/events/live-cycle.js"), page_id }),
|
||||||
|
|
||||||
el("div", { className: "note" }).append(
|
el("div", { className: "note" }).append(
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
For regular elements (non-custom elements), dd<el> uses ${el("a",
|
For regular elements (non-custom elements), DDE uses
|
||||||
references.mdn_mutation).append(el("code", "MutationObserver"), " | MDN")} internally to track
|
${el("a", references.mdn_mutation).append(el("code", "MutationObserver"), " | MDN")}
|
||||||
lifecycle events.
|
internally to track 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 as library must ensure proper (MutationObserver)
|
Always use ${el("code", "on.*")} functions, not ${el("code", "on('dde:*', ...)")}, for proper registration
|
||||||
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`
|
||||||
dd<el> ensures that connected/disconnected events fire only once for better predictability
|
DDE ensures that connected/disconnected events fire only once for better predictability
|
||||||
`)
|
`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`Utility Helpers`),
|
|
||||||
el("p").append(T`
|
|
||||||
You can use the ${el("code", "on.defer")} helper to defer execution to the next event loop.
|
|
||||||
This is useful for example when you wan to set some element properties based on the current element
|
|
||||||
body (typically the ${el("code", "<select value=\"...\">")}).
|
|
||||||
`),
|
|
||||||
el("div", { className: "function-table" }).append(
|
|
||||||
el("dl").append(
|
|
||||||
el("dt", t`on.defer(callback)`),
|
|
||||||
el("dd", t`Helper that defers function execution to the next event loop (using setTimeout)`),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
el(h3, t`Dispatching Custom Events`),
|
el(h3, t`Dispatching Custom Events`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
This makes it easy to implement component communication through events, following standard web platform
|
This makes it easy to implement component communication through events,
|
||||||
patterns. The curried approach allows for easy reuse of event dispatchers throughout your application.
|
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(example, { src: fileURL("./components/examples/events/compareDispatch.js"), page_id }),
|
||||||
el(code, { src: fileURL("./components/examples/events/dispatch.js") }),
|
|
||||||
|
|
||||||
el(h3, t`Best Practices`),
|
el(h3, t`Best Practices`),
|
||||||
el("ol").append(
|
el("ol").append(
|
||||||
el("li").append(T`
|
el("li").append(...T`
|
||||||
${el("strong", "Clean up listeners")}: Use AbortSignal to prevent memory leaks
|
${el("strong", "Clean up listeners")}: Use AbortSignal to prevent memory leaks
|
||||||
`),
|
`),
|
||||||
el("li").append(T`
|
el("li").append(...T`
|
||||||
${el("strong", "Leverage lifecycle events")}: For component setup and teardown
|
${el("strong", "Leverage lifecycle events")}: For component setup and teardown
|
||||||
`),
|
`),
|
||||||
el("li").append(T`
|
el("li").append(...T`
|
||||||
${el("strong", "Delegate when possible")}: Add listeners to container elements when handling many
|
${el("strong", "Delegate when possible")}: Add listeners to container elements when handling many similar elements
|
||||||
similar elements
|
|
||||||
`),
|
`),
|
||||||
el("li").append(T`
|
el("li").append(...T`
|
||||||
${el("strong", "Maintain consistency")}: Choose one event binding approach and stick with it
|
${el("strong", "Maintain consistency")}: Choose one event binding approach and stick with it
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
@ -250,6 +237,6 @@ export function page({ pkg, info }){
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(mnemonic),
|
el(mnemonic)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -42,128 +42,127 @@ const references= {
|
|||||||
};
|
};
|
||||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||||
export function page({ pkg, info }){
|
export function page({ pkg, info }){
|
||||||
|
const page_id= info.id;
|
||||||
return el(simplePage, { info, pkg }).append(
|
return el(simplePage, { info, pkg }).append(
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Signals provide a simple yet powerful way to create reactive applications with dd<el>. They handle the
|
Signals provide a simple yet powerful way to create reactive applications with DDE. 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", { className: "callout" }).append(
|
el("div", { class: "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") }),
|
el(code, { src: fileURL("./components/examples/signals/intro.js"), page_id }),
|
||||||
|
|
||||||
el(h3, t`The 3-Part Structure of Signals`),
|
el(h3, t`The 3-Part Structure of Signals`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Signals organize your code into three distinct parts, following the
|
Signals organize your code into three distinct parts, following the
|
||||||
${el("a", { textContent: t`3PS principle`, href: "./#h-3ps" })}:
|
${el("a", { textContent: t`3PS principle`, href: "./#h-3ps" })}:
|
||||||
`),
|
`),
|
||||||
el("div", { className: "signal-diagram" }).append(
|
el("div", { class: "signal-diagram" }).append(
|
||||||
el("div", { className: "signal-part" }).append(
|
el("div", { class: "signal-part" }).append(
|
||||||
el("h4", t`PART 1: Create Signal`),
|
el("h4", t`PART 1: Create Signal`),
|
||||||
el(code, { content: "const count = S(0);", language: "js" }),
|
el(code, { content: "const count = S(0);", page_id }),
|
||||||
el("p", t`Define a reactive value that can be observed and changed`)
|
el("p", t`Define a reactive value that can be observed and changed`)
|
||||||
),
|
),
|
||||||
el("div", { className: "signal-part" }).append(
|
el("div", { class: "signal-part" }).append(
|
||||||
el("h4", t`PART 2: React to Changes`),
|
el("h4", t`PART 2: React to Changes`),
|
||||||
el(code, { content: "S.on(count, value => updateUI(value));", language: "js" }),
|
el(code, { content: "S.on(count, value => updateUI(value));", page_id }),
|
||||||
el("p", t`Subscribe to signal changes with callbacks or effects`)
|
el("p", t`Subscribe to signal changes with callbacks or effects`)
|
||||||
),
|
),
|
||||||
el("div", { className: "signal-part" }).append(
|
el("div", { class: "signal-part" }).append(
|
||||||
el("h4", t`PART 3: Update Signal`),
|
el("h4", t`PART 3: Update Signal`),
|
||||||
el(code, { content: "count.set(count.get() + 1);", language: "js" }),
|
el(code, { content: "count.set(count.get() + 1);", page_id }),
|
||||||
el("p", t`Modify the signal value, which automatically triggers updates`)
|
el("p", t`Modify the signal value, which automatically triggers updates`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/signals/signals.js") }),
|
el(example, { src: fileURL("./components/examples/signals/signals.js"), page_id }),
|
||||||
|
|
||||||
el("div", { className: "note" }).append(
|
el("div", { class: "note" }).append(
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Signals implement the ${el("a", { textContent: t`Publish–subscribe pattern`, ...references.wiki_pubsub
|
Signals implement the ${el("a", { textContent: t`Publish–subscribe pattern`, ...references.wiki_pubsub })},
|
||||||
})}, a form of ${el("a", { textContent: t`Event-driven programming`, ...references.wiki_event_driven
|
a form of ${el("a", { textContent: t`Event-driven programming`, ...references.wiki_event_driven })}.
|
||||||
})}. This architecture allows different parts of your application to stay synchronized through
|
This architecture allows different parts of your application to stay synchronized through a shared signal,
|
||||||
a shared signal, without direct dependencies on each other. Compare for example with ${el("a", {
|
without direct dependencies on each other. Compare for example with ${el("a", { textContent:
|
||||||
textContent: t`fpubsub library`, ...references.fpubsub })}.
|
t`fpubsub library`, ...references.fpubsub })}.
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`Signal Essentials: Core API`),
|
el(h3, t`Signal Essentials: Core API`),
|
||||||
el("div", { className: "function-table" }).append(
|
el("div", { class: "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 ${el("a", { textContent:
|
Signals can be created with any type of value, but they work best with
|
||||||
t`primitive types`, ...references.mdn_primitive })} like strings, numbers, and booleans. For complex
|
${el("a", { textContent: t`primitive types`, ...references.mdn_primitive })} like strings, numbers, and booleans.
|
||||||
data types like objects and arrays, you’ll want to use Actions (covered below).
|
For complex data types like objects and arrays, you'll want to use Actions (covered below).
|
||||||
`),
|
`),
|
||||||
|
|
||||||
el(h3, t`Derived Signals: Computed Values`),
|
el(h3, t`Derived Signals: Computed Values`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Computed values (also called derived signals) automatically update when their dependencies change.
|
Computed values (also called derived signals) automatically update when their dependencies change.
|
||||||
Create them by passing ${el("strong", "a function")} to ${el("code", "S()")}:
|
Create them by passing a function to S():
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/signals/derived.js") }),
|
el(example, { src: fileURL("./components/examples/signals/derived.js"), page_id }),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Derived signals are read-only - you can’t call ${el("code", ".set()")} on them. Their value is always
|
Derived signals are read-only - you can't call .set() on them. Their value is always computed
|
||||||
computed from their dependencies. They’re perfect for transforming or combining data from other signals.
|
from their dependencies. They're perfect for transforming or combining data from other signals.
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/signals/computations-abort.js") }),
|
el(example, { src: fileURL("./components/examples/signals/computations-abort.js"), page_id }),
|
||||||
|
|
||||||
el(h3, t`Signal Actions: For Complex State`),
|
el(h3, t`Signal Actions: For Complex State`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
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", { className: "illustration" }).append(
|
el("div", { class: "illustration" }).append(
|
||||||
el("h4", t`Actions vs. Direct Mutation`),
|
el("h4", t`Actions vs. Direct Mutation`),
|
||||||
el("div", { className: "comparison" }).append(
|
el("div", { class: "comparison" }).append(
|
||||||
el("div", { className: "good-practice" }).append(
|
el("div", { class: "good-practice" }).append(
|
||||||
el("h5", t`✅ With Actions`),
|
el("h5", t`✅ With Actions`),
|
||||||
el(code, { content: `
|
el(code, { content: `const todos = S([], {
|
||||||
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");
|
S.action(todos, "add", "New todo");`, page_id })
|
||||||
`, language: "js" })
|
|
||||||
),
|
),
|
||||||
el("div", { className: "bad-practice" }).append(
|
el("div", { class: "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!
|
// This WON'T trigger updates!`, page_id }))
|
||||||
`, language: "js" }))
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
In some way, you can compare it with ${el("a", { textContent: "useReducer", ...references.mdn_use_reducer })}
|
In some way, you can compare it with ${el("a", { textContent: "useReducer", ...references.mdn_use_reducer })}
|
||||||
hook from React. So, the ${el("code", "S(<data>, <actions>)")} pattern creates a store “machine”. We can
|
hook from React. So, the ${el("code", "S(<data>, <actions>)")} pattern creates a store “machine”. We can
|
||||||
then invoke (dispatch) registered action by calling ${el("code", "S.action(<signal>, <name>, ...<args>)")}
|
then invoke (dispatch) registered action by calling ${el("code", "S.action(<signal>, <name>, ...<args>)")}
|
||||||
@ -171,9 +170,9 @@ export function page({ pkg, info }){
|
|||||||
${el("code", "this.stopPropagation()")} in the method representing the given action. As it can be seen in
|
${el("code", "this.stopPropagation()")} in the method representing the given action. As it can be seen in
|
||||||
examples, the “store” value is available also in the function for given action (${el("code", "this.value")}).
|
examples, the “store” value is available also in the function for given action (${el("code", "this.value")}).
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/signals/actions-demo.js") }),
|
el(example, { src: fileURL("./components/examples/signals/actions-demo.js"), page_id }),
|
||||||
|
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Actions provide these benefits:
|
Actions provide these benefits:
|
||||||
`),
|
`),
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
@ -182,37 +181,33 @@ export function page({ pkg, info }){
|
|||||||
el("li", t`Prevent accidental direct mutations`),
|
el("li", t`Prevent accidental direct mutations`),
|
||||||
el("li", t`Act similar to reducers in other state management libraries`)
|
el("li", t`Act similar to reducers in other state management libraries`)
|
||||||
),
|
),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Here’s a more complete example of a todo list using signal actions:
|
Here's a more complete example of a todo list using signal actions:
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/signals/actions-todos.js") }),
|
el(example, { src: fileURL("./components/examples/signals/actions-todos.js"), page_id }),
|
||||||
|
|
||||||
el("div", { className: "tip" }).append(
|
el("div", { class: "tip" }).append(
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
${el("strong", "Special Action Methods")}: Signal actions can implement special lifecycle hooks:
|
${el("strong", "Special Action Methods")}: Signal actions can implement special lifecycle hooks:
|
||||||
`),
|
`),
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
el("li").append(T`
|
el("li", t`[S.symbols.onclear]() - Called when the signal is cleared. Use it to clean up resources.`),
|
||||||
${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. dd<el> provides several ways to bind signals to DOM elements:
|
Signals really shine when connected to your UI. DDE provides several ways to bind signals to DOM elements:
|
||||||
`),
|
`),
|
||||||
|
|
||||||
el("div", { className: "tabs" }).append(
|
el("div", { class: "tabs" }).append(
|
||||||
el("div", { className: "tab", dataTab: "attributes" }).append(
|
el("div", { class: "tab", "data-tab": "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: `
|
el(code, { content: `// Create a signal
|
||||||
// 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
|
||||||
@ -221,17 +216,15 @@ export function page({ pkg, info }){
|
|||||||
}, "This text changes color");
|
}, "This text changes color");
|
||||||
|
|
||||||
// Later:
|
// Later:
|
||||||
color.set("red"); // UI updates automatically
|
color.set("red"); // UI updates automatically`, page_id })
|
||||||
`, language: "js" }),
|
|
||||||
),
|
),
|
||||||
el("div", { className: "tab", dataTab: "elements" }).append(
|
el("div", { class: "tab", "data-tab": "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: `
|
el(code, { content: `// Create an array signal
|
||||||
// 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))
|
||||||
@ -239,129 +232,61 @@ export function page({ pkg, info }){
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Later:
|
// Later:
|
||||||
S.action(items, "push", "Dragonfruit"); // List updates automatically
|
S.action(items, "push", "Dragonfruit"); // List updates automatically`, page_id })
|
||||||
`, language: "js" }),
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
The ${el("code", "assign")} and ${el("code", "el")} functions detect signals automatically and handle binding.
|
The ${el("code", "assign")} and ${el("code", "el")} functions detect signals automatically and handle binding.
|
||||||
You can use special properties like ${el("code", "dataset")}, ${el("code", "ariaset")}, and
|
You can use special properties like ${el("code", "dataset")}, ${el("code", "ariaset")}, and
|
||||||
${el("code", "classList")} for fine-grained control over specific attribute types.
|
${el("code", "classList")} for fine-grained control over specific attribute types.
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/signals/dom-attrs.js") }),
|
el(example, { src: fileURL("./components/examples/signals/dom-attrs.js"), page_id }),
|
||||||
|
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
${el("code", "S.el()")} is especially powerful for conditional rendering and lists:
|
${el("code", "S.el()")} is especially powerful for conditional rendering and lists:
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/signals/dom-el.js") }),
|
el(example, { src: fileURL("./components/examples/signals/dom-el.js"), page_id }),
|
||||||
|
|
||||||
el(h3, t`Best Practices for Signals`),
|
el(h3, t`Best Practices for Signals`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Follow these guidelines to get the most out of signals:
|
Follow these guidelines to get the most out of signals:
|
||||||
`),
|
`),
|
||||||
el("ol").append(
|
el("ol").append(
|
||||||
el("li").append(T`
|
el("li").append(...T`
|
||||||
${el("strong", "Keep signals small and focused")}: Use many small signals rather than a few large ones
|
${el("strong", "Keep signals small and focused")}: Use many small signals rather than a few large ones
|
||||||
`),
|
`),
|
||||||
el("li").append(T`
|
el("li").append(...T`
|
||||||
${el("strong", "Use derived signals for computations")}: Don’t recompute values in multiple places
|
${el("strong", "Use derived signals for computations")}: Don't recompute values in multiple places
|
||||||
`),
|
`),
|
||||||
el("li").append(T`
|
el("li").append(...T`
|
||||||
${el("strong", "Clean up signal subscriptions")}: Use AbortController (scope.host()) to prevent memory
|
${el("strong", "Clean up signal subscriptions")}: Use AbortController or scope.host() to prevent memory leaks
|
||||||
leaks
|
|
||||||
`),
|
`),
|
||||||
el("li").append(T`
|
el("li").append(...T`
|
||||||
${el("strong", "Use actions for complex state")}: Don’t directly mutate objects or arrays in signals
|
${el("strong", "Use actions for complex state")}: Don't directly mutate objects or arrays in signals
|
||||||
`),
|
`),
|
||||||
el("li").append(T`
|
el("li").append(...T`
|
||||||
${el("strong", "Avoid infinite loops")}: Be careful when one signal updates another in a subscription
|
${el("strong", "Avoid infinite loops")}: Be careful when one signal updates another in a subscription
|
||||||
`),
|
`)
|
||||||
),
|
|
||||||
el("p").append(T`
|
|
||||||
While signals provide powerful reactivity for complex UI interactions, they’re not always necessary.
|
|
||||||
A good approach is to started with variables/constants and when necessary, convert them to signals.
|
|
||||||
`),
|
|
||||||
|
|
||||||
el("div", { className: "tabs" }).append(
|
|
||||||
el("div", { className: "tab", dataTab: "events" }).append(
|
|
||||||
el("h4", t`We can process form events without signals`),
|
|
||||||
el("p", t`This can be used when the form data doesn’t need to be reactive and we just waiting for
|
|
||||||
results.`),
|
|
||||||
el(code, { content: `
|
|
||||||
const onFormSubmit = on("submit", e => {
|
|
||||||
e.preventDefault();
|
|
||||||
const formData = new FormData(e.currentTarget);
|
|
||||||
// this can be sent to a server
|
|
||||||
// or processed locally
|
|
||||||
// e.g.: console.log(Object.fromEntries(formData))
|
|
||||||
});
|
|
||||||
// …
|
|
||||||
return el("form", null, onFormSubmit).append(
|
|
||||||
// …
|
|
||||||
);
|
|
||||||
`, language: "js" })
|
|
||||||
),
|
),
|
||||||
|
|
||||||
el("div", { className: "tab", dataTab: "variables" }).append(
|
el("div", { class: "troubleshooting" }).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, pre } from "./components/code.html.js";
|
import { code } 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,34 +27,35 @@ const references= {
|
|||||||
};
|
};
|
||||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||||
export function page({ pkg, info }){
|
export function page({ pkg, info }){
|
||||||
|
const page_id= info.id;
|
||||||
return el(simplePage, { info, pkg }).append(
|
return el(simplePage, { info, pkg }).append(
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
For state-less components we can use functions as UI components (see “Elements” page). But in real life,
|
For state-less components we can use functions as UI components (see “Elements” page). But in real life,
|
||||||
we may need to handle the component’s life-cycle and provide JavaScript the way to properly use
|
we may need to handle the component's life-cycle and provide JavaScript the way to properly use
|
||||||
the ${el("a", { textContent: t`Garbage collection`, ...references.garbage_collection })}.
|
the ${el("a", { textContent: t`Garbage collection`, ...references.garbage_collection })}.
|
||||||
`),
|
`),
|
||||||
el(code, { src: fileURL("./components/examples/scopes/intro.js") }),
|
el(code, { src: fileURL("./components/examples/scopes/intro.js"), page_id }),
|
||||||
el("p").append(T`The library therefore uses ${el("em", t`scopes`)} to provide these functionalities.`),
|
el("p").append(...T`The library therefore uses ${el("em", t`scopes`)} to provide these functionalities.`),
|
||||||
|
|
||||||
el(h3, t`Understanding Host Elements and Scopes`),
|
el(h3, t`Understanding Host Elements and Scopes`),
|
||||||
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, { content: `
|
el("pre").append(el("code", `
|
||||||
// 1. Component scope created
|
// 1. Component scope created
|
||||||
el(MyComponent);
|
el(MyComponent);
|
||||||
|
|
||||||
function MyComponent() {
|
function MyComponent() {
|
||||||
// 2. access the host element (or other scope related values)
|
// 2. access the host element
|
||||||
const { host } = scope;
|
const { host } = scope;
|
||||||
|
|
||||||
// 3. Add behavior to host
|
// 3. Add behavior to host
|
||||||
@ -67,119 +68,113 @@ export function page({ pkg, info }){
|
|||||||
className: "my-component"
|
className: "my-component"
|
||||||
}).append(
|
}).append(
|
||||||
el("h2", "Title"),
|
el("h2", "Title"),
|
||||||
el("p", "Content"),
|
el("p", "Content")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
` })
|
`.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") }),
|
el(example, { src: fileURL("./components/examples/scopes/scopes-and-hosts.js"), page_id }),
|
||||||
|
|
||||||
el("div", { className: "tip" }).append(
|
el("div", { className: "tip" }).append(
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
${el("strong", "Best Practice:")} Always capture the host reference (or other scope related values) at
|
${el("strong", "Best Practice:")} Always capture the host reference at the beginning of your component
|
||||||
the beginning of your component function using ${el("code", "const { host } = scope")} to avoid
|
function using ${el("code", "const { host } = scope")} to avoid scope-related issues, especially with
|
||||||
scope-related issues, especially with ${el("em", "asynchronous code")}.
|
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") }),
|
el(code, { src: fileURL("./components/examples/scopes/good-practise.js"), page_id }),
|
||||||
|
|
||||||
el(h3, t`Class-Based Components`),
|
el(h3, t`Class-Based Components`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
While functional components are the primary pattern in dd<el>, you can also create class-based components.
|
While functional components are the primary pattern in DDE, 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") }),
|
el(example, { src: fileURL("./components/examples/scopes/class-component.js"), page_id }),
|
||||||
|
|
||||||
el(h3, t`Automatic Cleanup with Scopes`),
|
el(h3, t`Automatic Cleanup with Scopes`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
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, { content: `
|
el("pre").append(el("code", `
|
||||||
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 (browser)
|
- Event listeners
|
||||||
- Signal subscriptions (dd<el> and browser)
|
- Signal subscriptions
|
||||||
- Custom cleanup code (dd<el> and user)
|
- Custom cleanup code
|
||||||
` })
|
`))
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/scopes/cleaning.js") }),
|
el(example, { src: fileURL("./components/examples/scopes/cleaning.js"), page_id }),
|
||||||
|
|
||||||
el("div", { className: "note" }).append(
|
el("div", { className: "note" }).append(
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
In this example, when you click "Remove", the component is removed from the DOM, and all its associated
|
In this example, when you click "Remove", the component is removed from the DOM, and all its associated
|
||||||
resources are automatically cleaned up, including ${el("em",
|
resources are automatically cleaned up, including the signal subscription that updates the text content.
|
||||||
"the signal subscription that updates the text content")}. This happens because the library
|
This happens because the library internally registers a disconnected event handler on the host element.
|
||||||
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 (3PS)", ...references.signals })}.
|
into three parts as introduced in ${el("a", { textContent: "Signals", ...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", dataTab: "declarative" }).append(
|
el("div", { className: "tab", "data-tab": "declarative" }).append(
|
||||||
el("h4", t`✅ Declarative Approach`),
|
el("h4", t`✅ Declarative Approach`),
|
||||||
el("p", t`Define what your UI should look like based on state:`),
|
el("p", t`Define what your UI should look like based on state:`),
|
||||||
el(code, { src: fileURL("./components/examples/scopes/declarative.js") })
|
el(code, { src: fileURL("./components/examples/scopes/declarative.js"), page_id })
|
||||||
),
|
),
|
||||||
el("div", { className: "tab", dataTab: "imperative" }).append(
|
el("div", { className: "tab", "data-tab": "imperative" }).append(
|
||||||
el("h4", t`⚠️ Imperative Approach`),
|
el("h4", t`⚠️ Imperative Approach`),
|
||||||
el("p", t`Manually update the DOM in response to events:`),
|
el("p", t`Manually update the DOM in response to events:`),
|
||||||
el(code, { src: fileURL("./components/examples/scopes/imperative.js") })
|
el(code, { src: fileURL("./components/examples/scopes/imperative.js"), page_id })
|
||||||
),
|
),
|
||||||
el("div", { className: "tab", dataTab: "mixed" }).append(
|
el("div", { className: "tab", "data-tab": "mixed" }).append(
|
||||||
el("h4", t`❌ Mixed Approach`),
|
el("h4", t`❌ Mixed Approach`),
|
||||||
el("p", t`This approach should be avoided:`),
|
el("p", t`This approach should be avoided:`),
|
||||||
el(code, { src: fileURL("./components/examples/scopes/mixed.js") })
|
el(code, { src: fileURL("./components/examples/scopes/mixed.js"), page_id })
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
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", "Prefer declarative patterns:")} Use signals to drive UI updates rather than manual DOM manipulation
|
||||||
`),
|
`),
|
||||||
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("li").append(T`
|
|
||||||
${el("strong", "Keep components focused:")} Each component should do one thing well
|
${el("strong", "Keep components focused:")} Each component should do one thing well
|
||||||
`),
|
`),
|
||||||
el("li").append(T`
|
el("li").append(...T`
|
||||||
${el("strong", "Add explicit cleanup:")} For resources not managed by dd<el>, use ${el("code",
|
${el("strong", "Add explicit cleanup:")} For resources not managed by DDE, use ${el("code", "on.disconnected")}
|
||||||
"on.disconnected")}
|
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
|
|
||||||
@ -196,7 +191,7 @@ export function page({ pkg, info }){
|
|||||||
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(s)`)
|
el("dd", t`Choose one approach and be consistent throughout a component`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -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 dd<el>: Better Together`,
|
fullTitle: t`Using Web Components with DDE: Better Together`,
|
||||||
description: t`Using custom elements in combination with dd<el>`,
|
description: t`Using custom elements in combination with DDE`,
|
||||||
};
|
};
|
||||||
|
|
||||||
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, pre } from "./components/code.html.js";
|
import { code } 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,55 +49,51 @@ 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`
|
||||||
dd<el> pairs powerfully with ${el("a", references.mdn_web_components).append(el("strong", t`Web
|
DDE pairs powerfully with ${el("a", references.mdn_web_components).append(el("strong", t`Web Components`))}
|
||||||
Components`))} to create reusable, encapsulated custom elements with all the benefits of dd<el>’s
|
to create reusable, encapsulated custom elements with all the benefits of DDE's declarative DOM
|
||||||
declarative DOM construction and reactivity system.
|
construction and reactivity system.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "callout" }).append(
|
el("div", { className: "callout" }).append(
|
||||||
el("h4", t`Why Combine dd<el> with Web Components?`),
|
el("h4", t`Why Combine DDE 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("em",
|
${el("strong", "HTML Templates:")} Define reusable markup structures
|
||||||
"the dd<el> replaces this part")})
|
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Let’s start with a basic Custom Element example without dd<el> to establish the foundation:
|
Let's start with a basic Custom Element example without DDE to establish the foundation:
|
||||||
`),
|
`),
|
||||||
el(code, { src: fileURL("./components/examples/customElement/native-basic.js") }),
|
el(code, { src: fileURL("./components/examples/customElement/native-basic.js"), page_id }),
|
||||||
|
|
||||||
el("div", { className: "note" }).append(
|
el("div", { className: "note" }).append(
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
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`))}
|
||||||
@ -105,163 +101,160 @@ export function page({ pkg, info }){
|
|||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`dd<el> Integration: Step 1 - Event Handling`),
|
el(h3, t`DDE Integration: Step 1 - Event Handling`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
The first step in integrating dd<el> with Web Components is enabling dd<el>’s event system to work with your
|
The first step in integrating DDE with Web Components is enabling DDE's event system to work with your
|
||||||
Custom Elements. This is done with ${el("code", "customElementWithDDE")}, which makes your Custom Element
|
Custom Elements. This is done with ${el("code", "customElementWithDDE")}, which makes your Custom Element
|
||||||
compatible with dd<el>’s event handling. (${el("em").append(T`Notice that customElementWithDDE is
|
compatible with DDE's event handling.
|
||||||
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 dd<el>’s event system to work with your Custom Element`),
|
el("dd", t`Enables DDE'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() or S.observedAttributes().`)
|
el("dd", t`Allows using on.connected(), on.disconnected(), etc. with your element`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/customElement/customElementWithDDE.js") }),
|
el(example, { src: fileURL("./components/examples/customElement/customElementWithDDE.js"), page_id }),
|
||||||
|
|
||||||
el("div", { className: "tip" }).append(
|
el("div", { className: "tip" }).append(
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
${el("strong", "Key Point:")} The ${el("code", "customElementWithDDE")} function adds event dispatching
|
${el("strong", "Key Point:")} The ${el("code", "customElementWithDDE")} function adds event dispatching
|
||||||
to your Custom Element lifecycle methods, making them work seamlessly with dd<el>’s event system.
|
to your Custom Element lifecycle methods, making them work seamlessly with DDE's event system.
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`dd<el> Integration: Step 2 - Rendering Components`),
|
el(h3, t`DDE Integration: Step 2 - Rendering Components`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
The next step is to use dd<el>’s component rendering within your Custom Element. This is done with
|
The next step is to use DDE's component rendering within your Custom Element. This is done with
|
||||||
${el("code", "customElementRender")}, which connects your dd<el> component function to the Custom Element.
|
${el("code", "customElementRender")}, which connects your DDE 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 dd<el> component function to a Custom Element`),
|
el("dd", t`Connects a DDE 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 (empty by default or
|
el("li", t`Optional: Attributes transformer function (default or S.observedAttributes)`)
|
||||||
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") }),
|
el(example, { src: fileURL("./components/examples/customElement/dde.js"), page_id }),
|
||||||
|
|
||||||
el("div", { className: "note" }).append(
|
el("div", { className: "note" }).append(
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
In this example, we’re using Shadow DOM (${el("code", "this.attachShadow()")}) for encapsulation,
|
In this example, we're using Shadow DOM (${el("code", "this.attachShadow()")}) for encapsulation,
|
||||||
but you can also render directly to the element with ${el("code", "customElementRender(this, ...)")}.
|
but you can also render directly to the element with ${el("code", "customElementRender(this, ...)")}.
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`Reactive Web Components with Signals`),
|
el(h3, t`Reactive Web Components with Signals`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
One of the most powerful features of integrating dd<el> with Web Components is connecting HTML attributes
|
One of the most powerful features of integrating DDE with Web Components is connecting HTML attributes
|
||||||
to dd<el>’s reactive signals system. This creates truly reactive custom elements.
|
to DDE's reactive signals system. This creates truly reactive custom elements.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "tip" }).append(
|
el("div", { className: "tip" }).append(
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
${el("strong", "Two Ways to Handle Attributes:")}
|
${el("strong", "Two Ways to Handle Attributes:")}
|
||||||
`),
|
`),
|
||||||
el("ol").append(
|
el("ol").append(
|
||||||
el("li").append(T`
|
el("li").append(...T`
|
||||||
Using standard attribute access (${el("code", "this.getAttribute(<name>)")}) - Passes attributes as
|
${el("code", "observedAttributes")} - Passes attributes as regular values (static)
|
||||||
regular values (static)
|
|
||||||
`),
|
`),
|
||||||
el("li").append(T`
|
el("li").append(...T`
|
||||||
${el("code", "S.observedAttributes")} - Transforms attributes into signals (reactive)
|
${el("code", "S.observedAttributes")} - Transforms attributes into signals (reactive)
|
||||||
`)
|
`)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Using the ${el("code", "S.observedAttributes")} creates a reactive connection between your element’s
|
Using ${el("code", "S.observedAttributes")} creates a reactive connection between your element's attributes
|
||||||
attributes and its internal rendering. When attributes change, your component automatically updates!
|
and its internal rendering. When attributes change, your component automatically updates!
|
||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/customElement/observedAttributes.js") }),
|
el(example, { src: fileURL("./components/examples/customElement/observedAttributes.js"), page_id }),
|
||||||
|
|
||||||
el("div", { className: "callout" }).append(
|
el("div", { className: "callout" }).append(
|
||||||
el("h4", t`How S.observedAttributes Works`),
|
el("h4", t`How S.observedAttributes Works`),
|
||||||
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 dd<el> signal for each one`),
|
el("li", t`Creates a DDE 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 dd<el> with Shadow DOM,
|
Shadow DOM provides encapsulation for your component's styles and markup. When using DDE 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, { content: `
|
el("pre").append(el("code", `
|
||||||
<my-custom-element>
|
<my-custom-element>
|
||||||
|
|
||||||
┌─────────────────────────┐
|
┌─────────────────────────┐
|
||||||
#shadow-root
|
#shadow-root
|
||||||
|
|
||||||
Created with dd<el>
|
Created with DDE:
|
||||||
┌──────────────────┐
|
┌──────────────────┐
|
||||||
<div>
|
<div>
|
||||||
<h2>Title</h2>
|
<h2>Title</h2>
|
||||||
<p>Content</p>
|
<p>Content</p>
|
||||||
` })
|
`))
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js") }),
|
el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js"), page_id }),
|
||||||
|
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
For more information on Shadow DOM, see
|
For more information on Shadow DOM, see
|
||||||
${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") }),
|
el(example, { src: fileURL("./components/examples/customElement/simulateSlots.js"), page_id }),
|
||||||
el("div", { className: "function-table" }).append(
|
el("div", { className: "function-table" }).append(
|
||||||
el("h4", t`simulateSlots`),
|
el("h4", t`simulateSlots`),
|
||||||
el("dl").append(
|
el("dl").append(
|
||||||
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 dd<el>`),
|
el(h3, t`Best Practices for Web Components with DDE`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
When combining dd<el> with Web Components, follow these recommendations:
|
When combining DDE 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
|
||||||
`)
|
`)
|
||||||
),
|
),
|
||||||
@ -272,7 +265,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 dd<el>`,
|
fullTitle: t`Debugging applications with deka-dom-el`,
|
||||||
description: t`Techniques for debugging applications using dd<el>, especially signals.`,
|
description: t`Techniques for debugging applications using deka-dom-el, especially signals.`,
|
||||||
};
|
};
|
||||||
|
|
||||||
import { el } from "deka-dom-el";
|
import { el } from "deka-dom-el";
|
||||||
@ -15,207 +15,178 @@ const fileURL= url=> new URL(url, import.meta.url);
|
|||||||
|
|
||||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||||
export function page({ pkg, info }){
|
export function page({ pkg, info }){
|
||||||
|
const page_id= info.id;
|
||||||
return el(simplePage, { info, pkg }).append(
|
return el(simplePage, { info, pkg }).append(
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Debugging is an essential part of application development. This guide provides techniques
|
Debugging is an essential part of application development. This guide provides techniques
|
||||||
and best practices for debugging applications built with dd<el>, with a focus on signals.
|
and best practices for debugging applications built with deka-dom-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
|
you need to track their values, understand their dependencies, and identify why updates are or aren't happening.
|
||||||
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 or valueOf method:
|
The simplest way to debug a signal is to log its current value by calling the get method:
|
||||||
`),
|
`),
|
||||||
el(code, { content: `
|
el(code, { content: `
|
||||||
const signal = S(0);
|
const signal = S(0);
|
||||||
console.log('Current value:', signal.valueOf());
|
|
||||||
`, language: "js" }),
|
|
||||||
el("div", { className: "warning" }).append(
|
|
||||||
el("p").append(T`
|
|
||||||
${el("code", "signal.get")} is OK, but in some situations may lead to unexpected results:
|
|
||||||
`),
|
|
||||||
el(code, { content: `
|
|
||||||
const signal = S(0);
|
|
||||||
const derived = S(()=> {
|
|
||||||
console.log('Current value:', signal.get());
|
console.log('Current value:', signal.get());
|
||||||
// ↑ in rare cases this will register unwanted dependency
|
// without triggering updates
|
||||||
// but typically this is fine ↓
|
console.log('Current value:', signal.valueOf());
|
||||||
return signal.get() + 1;
|
`, page_id }),
|
||||||
});
|
el("p").append(...T`
|
||||||
`, language: "js" })
|
You can also monitor signal changes by adding a listener:
|
||||||
),
|
|
||||||
el("p").append(T`
|
|
||||||
You can also monitor signal changes by adding a listener:
|
|
||||||
`),
|
`),
|
||||||
el(code, { content: `
|
el(code, {
|
||||||
// Log every time the signal changes
|
content:
|
||||||
S.on(signal, value => console.log('Signal changed:', value));
|
"// Log every time the signal changes\nS.on(signal, value => console.log('Signal changed:', value));",
|
||||||
`, language: "js" }),
|
page_id }),
|
||||||
|
|
||||||
el("h4", t`Debugging derived signals`),
|
el("h4", t`Debugging derived signals`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
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/debugger inside the computation function to see when it runs`),
|
el("li", t`Add logging inside the computation function to see when it runs`),
|
||||||
el("li", t`Verify that the computation function actually accesses the signal values with .get()`)
|
el("li", t`Verify that the computation function actually accesses the signal values with .get()`)
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/debugging/consoleLog.js") }),
|
el(example, { src: fileURL("./components/examples/debugging/consoleLog.js"), page_id }),
|
||||||
|
|
||||||
el("h4", t`Examining signal via DevTools`),
|
|
||||||
el("p").append(T`
|
|
||||||
${el("code", "<signal>.__dde_signal")} - A Symbol property used to identify and store the internal state of
|
|
||||||
signal objects. It contains the following information:
|
|
||||||
`),
|
|
||||||
el("ul").append(
|
|
||||||
// TODO: value?
|
|
||||||
el("li", t`listeners: A Set of functions called when the signal value changes`),
|
|
||||||
el("li", t`actions: Custom actions that can be performed on the signal`),
|
|
||||||
el("li", t`onclear: Functions to run when the signal is cleared`),
|
|
||||||
el("li", t`host: Reference to the host element/scope in which the signal was created`),
|
|
||||||
),
|
|
||||||
el("p").append(T`
|
|
||||||
…to determine the current value of the signal, call ${el("code", "signal.valueOf()")}. Don’t hesitate to
|
|
||||||
use the debugger to inspect the signal object.
|
|
||||||
`),
|
|
||||||
|
|
||||||
el("h4", t`Debugging with breakpoints`),
|
|
||||||
el("p").append(T`
|
|
||||||
Effective use of breakpoints can help track signal flow:
|
|
||||||
`),
|
|
||||||
el("ul").append(
|
|
||||||
el("li").append(T`
|
|
||||||
Set breakpoints in signal update methods to track when values change
|
|
||||||
`),
|
|
||||||
el("li").append(T`
|
|
||||||
Use conditional breakpoints to only break when specific signals change to certain values
|
|
||||||
`),
|
|
||||||
el("li").append(T`
|
|
||||||
Set breakpoints in your signal computation functions to see when derived signals recalculate
|
|
||||||
`),
|
|
||||||
el("li").append(T`
|
|
||||||
Use performance profiling to identify bottlenecks in signal updates
|
|
||||||
`)
|
|
||||||
),
|
|
||||||
|
|
||||||
el(h3, t`Common signal debugging issues`),
|
el(h3, t`Common signal debugging issues`),
|
||||||
el("h4", t`Signal updates not triggering UI changes`),
|
el("h4", t`Signal updates not triggering UI changes`),
|
||||||
el("p", t`If signal updates aren’t reflected in the UI, check:`),
|
el("p").append(...T`
|
||||||
el("ul").append(
|
If signal updates aren't reflected in the UI, check:
|
||||||
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("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") }),
|
el(code, { src: fileURL("./components/examples/debugging/mutations.js"), page_id }),
|
||||||
|
|
||||||
el("h4", t`Memory leaks with signal listeners`),
|
el("h4", t`Memory leaks with signal listeners`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Signal listeners can cause memory leaks if not properly cleaned up. Always use AbortSignal
|
Signal listeners can cause memory leaks if not properly cleaned up. Always use AbortSignal
|
||||||
to cancel listeners when they are used ouside the dd<el> knowledge (el, assign, S.el, … auto cleanup
|
to cancel listeners.
|
||||||
unnecessarily signals automatically).
|
|
||||||
`),
|
`),
|
||||||
|
|
||||||
el("h4", t`Performance issues with frequently updating signals`),
|
el("h4", t`Performance issues with frequently updating signals`),
|
||||||
el("p", t`If you notice performance issues with signals that update very frequently:`),
|
el("p").append(...T`
|
||||||
|
If you notice performance issues with signals that update very frequently:
|
||||||
|
`),
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
el("li", t`Consider debouncing or throttling signal updates`),
|
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`Make sure derived signals don't perform expensive calculations unnecessarily`),
|
||||||
el("li", t`Keep signal computations focused and minimal`)
|
el("li", t`Keep signal computations focused and minimal`)
|
||||||
),
|
),
|
||||||
el(code, { src: fileURL("./components/examples/debugging/debouncing.js") }),
|
el(code, { src: fileURL("./components/examples/debugging/debouncing.js"), page_id }),
|
||||||
|
|
||||||
el(h3, t`Browser DevTools tips for components and reactivity`),
|
el(h3, t`Browser DevTools tips for deka-dom-el`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
When debugging in the browser, dd<el> provides several helpful DevTools-friendly features:
|
When debugging in the browser, 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("h4", t`Finding reactive elements in the DOM`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
When using ${el("code", "S.el()")}, dd<el> creates reactive elements in the DOM
|
When using ${el("code", "S.el()")}, deka-dom-el creates reactive elements in the DOM
|
||||||
that are automatically updated when signal values change. These elements are wrapped in special
|
that are automatically updated when signal values change. These elements are wrapped in special
|
||||||
comment nodes for debugging (to be true they are also used internally, so please do not edit them by hand):
|
comment nodes for debugging (to be true they are also used internally, so please do not edit them by hand):
|
||||||
`),
|
`),
|
||||||
el(code, { src: fileURL("./components/examples/debugging/dom-reactive-mark.html") }),
|
el(code, { src: fileURL("./components/examples/debugging/dom-reactive-mark.js"), page_id }),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
This is particularly useful when debugging why a reactive section isn’t updating as expected.
|
This is particularly useful when debugging why a reactive section isn't updating as expected.
|
||||||
You can inspect the elements between the comment nodes to see their current state and the
|
You can inspect the elements between the comment nodes to see their current state and the
|
||||||
signal connections through \`__dde_reactive\` of the host element.
|
signal connections through \`__dde_reactive\` of the host element.
|
||||||
`),
|
`),
|
||||||
|
|
||||||
el("h4", t`Identifying components in the DOM`),
|
el("h4", t`DOM inspection properties`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
dd<el> marks components in the DOM with special comment nodes to help you identify component boundaries.
|
Elements created with the deka-dom-el library have special properties to aid in debugging:
|
||||||
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("p").append(...T`
|
||||||
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("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
|
${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
|
relationships. This allows you to quickly identify which elements are reactive and what signals they're
|
||||||
bound to. Each entry in the array contains:
|
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`Inspecting events and listeners in DevTools`),
|
el("h4", t`Examining signal connections`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Modern browser DevTools provide built-in tools for inspecting event listeners attached to DOM elements.
|
${el("code", "<signal>.__dde_signal")} - A Symbol property used to identify and store the internal state of
|
||||||
For example, in Firefox and Chrome, you can:
|
signal objects. It contains the following information:
|
||||||
`),
|
`),
|
||||||
el("ol").append(
|
el("ul").append(
|
||||||
el("li", t`Select an element in the Elements/Inspector panel`),
|
el("li", "listeners: A Set of functions called when the signal value changes"),
|
||||||
el("li", t`Look for the "Event Listeners" tab or section`),
|
el("li", "actions: Custom actions that can be performed on the signal"),
|
||||||
el("li", t`See all event listeners attached to the element, including those added by dd<el>`)
|
el("li", "onclear: Functions to run when the signal is cleared"),
|
||||||
|
el("li", "host: Reference to the host element/scope"),
|
||||||
|
el("li", "defined: Stack trace information for debugging"),
|
||||||
|
el("li", "readonly: Boolean flag indicating if the signal is read-only")
|
||||||
),
|
),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Additionally, dd<el> provides special markers in the DOM that help identify debug information.
|
…to determine the current value of the signal, call ${el("code", "signal.valueOf()")}.
|
||||||
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("p").append(...T`
|
||||||
el("img", {
|
You can inspect (host) element relationships and bindings with signals in the DevTools console using
|
||||||
src: "./assets/devtools.png",
|
${el("code", "$0.__dde_reactive")} (for currently selected element). In the console you will see a list of
|
||||||
alt: "Screenshot of DevTools showing usage of “event” button to inspect event listeners",
|
${el("code", "[ [ signal, listener ], element, property ]")}, where:
|
||||||
}),
|
`),
|
||||||
el("figcaption", t`Firefox DevTools showing dd<el> debugging information with event listeners and reactive
|
el("ul").append(
|
||||||
markers`)
|
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("p").append(...T`
|
||||||
|
Effective use of breakpoints can help track signal flow:
|
||||||
|
`),
|
||||||
|
el("ul").append(
|
||||||
|
el("li").append(...T`
|
||||||
|
Set breakpoints in signal update methods to track when values change
|
||||||
|
`),
|
||||||
|
el("li").append(...T`
|
||||||
|
Use conditional breakpoints to only break when specific signals change to certain values
|
||||||
|
`),
|
||||||
|
el("li").append(...T`
|
||||||
|
Set breakpoints in your signal computation functions to see when derived signals recalculate
|
||||||
|
`),
|
||||||
|
el("li").append(...T`
|
||||||
|
Use performance profiling to identify bottlenecks in signal updates
|
||||||
|
`)
|
||||||
|
),
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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 dd<el> with Third-Party Functionalities`,
|
fullTitle: t`Extending deka-dom-el with Third-Party Functionalities`,
|
||||||
description: t`How to extend dd<el> with third-party libraries and custom functionalities.`,
|
description: t`How to extend deka-dom-el with third-party libraries and custom functionalities.`,
|
||||||
};
|
};
|
||||||
|
|
||||||
import { el } from "deka-dom-el";
|
import { el } from "deka-dom-el";
|
||||||
@ -14,31 +14,32 @@ const fileURL= url=> new URL(url, import.meta.url);
|
|||||||
|
|
||||||
/** @param {import("./types.js").PageAttrs} attrs */
|
/** @param {import("./types.js").PageAttrs} attrs */
|
||||||
export function page({ pkg, info }){
|
export function page({ pkg, info }){
|
||||||
|
const page_id= info.id;
|
||||||
return el(simplePage, { info, pkg }).append(
|
return el(simplePage, { info, pkg }).append(
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
dd<el> is designed with extensibility in mind. This page covers how to separate
|
deka-dom-el is designed with extensibility in mind. This page covers how to separate
|
||||||
third-party functionalities and integrate them seamlessly with the library, focusing on
|
third-party functionalities and integrate them seamlessly with the library, focusing on
|
||||||
proper resource cleanup and interoperability.
|
proper resource cleanup and interoperability.
|
||||||
`),
|
`),
|
||||||
|
|
||||||
el(h3, t`DOM Element Extensions with Addons`),
|
el(h3, t`DOM Element Extensions with Addons`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
The primary method for extending DOM elements in dd<el> is through the Addon pattern.
|
The primary method for extending DOM elements in deka-dom-el is through the Addon pattern.
|
||||||
Addons are functions that take an element and applying some functionality to it. This pattern enables
|
Addons are functions that take an element and applying some functionality to it. This pattern enables a
|
||||||
a clean, functional approach to element enhancement.
|
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
|
||||||
@ -46,26 +47,29 @@ export function page({ pkg, info }){
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using an addon
|
// Using an addon
|
||||||
el("div", { id: "example" }, myAddon({ option: "value" }));
|
el("div", { id: "example" }, myAddon({ option: "value" }));
|
||||||
`, language: "js" }),
|
`.trim(), page_id }),
|
||||||
|
|
||||||
el(h3, t`Resource Cleanup with Abort Signals`),
|
el(h3, t`Resource Cleanup with Abort Signals`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
When extending elements with functionality that uses resources like event listeners, timers,
|
When extending elements with functionality that uses resources like event listeners, timers,
|
||||||
or external subscriptions, it’s critical to clean up these resources when the element is removed
|
or external subscriptions, it's critical to clean up these resources when the element is removed
|
||||||
from the DOM. dd<el> provides utilities for this through AbortSignal integration.
|
from the DOM. deka-dom-el provides utilities for this through AbortSignal integration.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "tip" }).append(
|
el("div", { className: "tip" }).append(
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
The ${el("code", "scope.signal")} property creates an AbortSignal that automatically
|
The ${el("code", "on.disconnectedAsAbort")} utility 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: `
|
||||||
// Third-party library addon with proper cleanup
|
// Third-party library addon with proper cleanup
|
||||||
function externalLibraryAddon(config, signal) {
|
function externalLibraryAddon(config, signal) {
|
||||||
return function(element) {
|
return function(element) {
|
||||||
|
// Get an abort signal that triggers on element disconnection
|
||||||
|
const signal = on.disconnectedAsAbort(element);
|
||||||
|
|
||||||
// Initialize the third-party library
|
// Initialize the third-party library
|
||||||
const instance = new ExternalLibrary(element, config);
|
const instance = new ExternalLibrary(element, config);
|
||||||
|
|
||||||
@ -79,17 +83,19 @@ export function page({ pkg, info }){
|
|||||||
}
|
}
|
||||||
// dde component
|
// dde component
|
||||||
function Component(){
|
function Component(){
|
||||||
const { signal }= scope;
|
const { host }= scope;
|
||||||
|
const signal= on.disconnectedAsAbort(host);
|
||||||
return el("div", null, externalLibraryAddon({ option: "value" }, signal));
|
return el("div", null, externalLibraryAddon({ option: "value" }, signal));
|
||||||
}
|
}
|
||||||
`, language: "js" }),
|
`.trim(), page_id }),
|
||||||
|
|
||||||
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`),
|
||||||
@ -103,12 +109,12 @@ export function page({ pkg, info }){
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
`, language: "js" })
|
`.trim(), page_id })
|
||||||
),
|
),
|
||||||
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 dd<el>
|
// Tightly coupled to deka-dom-el
|
||||||
function enhancementElement(config) {
|
function enhancementElement(config) {
|
||||||
return function(element) {
|
return function(element) {
|
||||||
// do something
|
// do something
|
||||||
@ -117,86 +123,27 @@ export function page({ pkg, info }){
|
|||||||
})(element);
|
})(element);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
`, language: "js" })
|
`.trim(), page_id })
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`Signal Extensions and Factory Patterns`),
|
el(h3, t`Signal Extensions and Future Compatibility`),
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
Unlike DOM elements, signal functionality in dd<el> currently lacks a standardized
|
Unlike DOM elements, signal functionality in deka-dom-el currently lacks a standardized
|
||||||
way to create library-independent extensions. This is because signals are implemented
|
way to create library-independent extensions. This is because signals are implemented
|
||||||
differently across libraries.
|
differently across libraries.
|
||||||
`),
|
`),
|
||||||
el("div", { className: "note" }).append(
|
el("div", { className: "note" }).append(
|
||||||
el("p").append(T`
|
el("p").append(...T`
|
||||||
In the future, JavaScript may include built-in signals through the
|
In the future, JavaScript may include built-in signals through the
|
||||||
${el("a", { href: "https://github.com/tc39/proposal-signals", textContent: "TC39 Signals Proposal" })}.
|
${el("a", { href: "https://github.com/tc39/proposal-signals", textContent: "TC39 Signals Proposal" })}.
|
||||||
dd<el> is designed with future compatibility in mind and will hopefully support these
|
deka-dom-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`
|
||||||
el("h4", t`The Signal Factory Pattern`),
|
For now, when extending signals functionality, focus on clear interfaces and isolation to make
|
||||||
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: `
|
||||||
@ -209,61 +156,54 @@ export function page({ pkg, info }){
|
|||||||
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 { signal, increment, decrement };
|
return Object.assign(signal, {
|
||||||
|
increment,
|
||||||
|
decrement
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage
|
// Usage
|
||||||
const counter = createEnhancedSignal(0);
|
const counter = createEnhancedSignal(0);
|
||||||
el("button", { textContent: "Increment", onclick: () => counter.increment() });
|
el("button")({ onclick: () => counter.increment() }, "Increment");
|
||||||
el("div", S.text\`Count: \${counter}\`);
|
el("div", S.text\`Count: \${counter}\`);
|
||||||
`, language: "js" }),
|
`.trim(), page_id }),
|
||||||
|
|
||||||
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: `
|
el(code, { content: `// Independent signals without DOM integration
|
||||||
// Independent signals without DOM integration
|
import { signal as S, isSignal } from "deka-dom-el/src/signals-lib";
|
||||||
import { signal, isSignal } from "deka-dom-el/src/signals-lib";
|
|
||||||
|
|
||||||
// Create and use signals as usual
|
// Create and use signals as usual
|
||||||
const count = signal(0);
|
const count = S(0);
|
||||||
const doubled = signal(() => count.get() * 2);
|
const doubled = S(() => count.get() * 2);
|
||||||
|
|
||||||
// Subscribe to changes
|
// Subscribe to changes
|
||||||
signal.on(count, value => console.log(value));
|
S.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
|
console.log(doubled.get()); // 10`, page_id }),
|
||||||
`, language: "js" }),
|
el("p").append(...T`
|
||||||
el("p").append(T`
|
|
||||||
The independent signals API includes all core functionality (${el("code", "S()")}, ${el("code", "S.on()")},
|
The independent signals API includes all core functionality (${el("code", "S()")}, ${el("code", "S.on()")},
|
||||||
${el("code", "S.action()")}).
|
${el("code", "S.action()")}).
|
||||||
`),
|
`),
|
||||||
el("div", { className: "callout" }).append(
|
el("div", { class: "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`),
|
||||||
@ -274,27 +214,23 @@ export function page({ pkg, info }){
|
|||||||
|
|
||||||
el(h3, t`Best Practices for Extensions`),
|
el(h3, t`Best Practices for Extensions`),
|
||||||
el("ol").append(
|
el("ol").append(
|
||||||
el("li").append(T`
|
el("li").append(...T`
|
||||||
${el("strong", "Use AbortSignals for cleanup:")} Always implement proper resource cleanup with
|
${el("strong", "Use AbortSignals for cleanup:")} Always implement proper resource cleanup with
|
||||||
${el("code", "scope.signal")} or similar mechanisms
|
${el("code", "on.disconnectedAsAbort")} 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
|
||||||
`)
|
`)
|
||||||
@ -304,21 +240,16 @@ export function page({ pkg, info }){
|
|||||||
el("h4", t`Common Extension Pitfalls`),
|
el("h4", t`Common Extension Pitfalls`),
|
||||||
el("dl").append(
|
el("dl").append(
|
||||||
el("dt", t`Leaking event listeners or resources`),
|
el("dt", t`Leaking event listeners or resources`),
|
||||||
el("dd", t`Always use AbortSignal-based cleanup to automatically remove listeners when elements
|
el("dd", t`Always use AbortSignal-based cleanup to automatically remove listeners when elements 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 dd<el>
|
el("dd", t`Focus on standard DOM APIs and clean interfaces rather than depending on deka-dom-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`)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -1,400 +0,0 @@
|
|||||||
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),
|
|
||||||
);
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user