@nativele/ts-dom 0.4.6 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/README.md +46 -891
- package/README.zh-TW.md +46 -896
- package/dist/{a11y-wWL9rie7.mjs → a11y-CV3nBgBn.mjs} +1 -1
- package/dist/cli.mjs +227 -22
- package/dist/index.d.mts +16 -6
- package/dist/index.mjs +4 -1
- package/dist/{lint-CP_i4VPA.mjs → lint-DfUAh7t3.mjs} +17 -2
- package/dist/oxlint-plugin.mjs +2 -2
- package/dist/vite-plugin.mjs +1 -1
- package/llms.txt +12 -7
- package/package.json +4 -2
- package/skills/README.md +135 -0
- package/skills/_template/SKILL.md +57 -0
- package/skills/nativele-angular/SKILL.md +339 -0
- package/skills/nativele-core/SKILL.md +177 -0
- package/skills/nativele-integration/SKILL.md +125 -0
- package/skills/nativele-react/SKILL.md +213 -0
- package/skills/nativele-svelte/SKILL.md +259 -0
- package/skills/nativele-vite/SKILL.md +197 -0
- package/skills/nativele-vue/SKILL.md +279 -0
package/skills/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Nativele Skills
|
|
2
|
+
|
|
3
|
+
Skills extend what Nativele can be used with, without growing the library: each one is a task procedure for a coding agent, verified with exact versions. This page is the specification every Skill follows and the guide for contributing one.
|
|
4
|
+
|
|
5
|
+
## What a Skill is
|
|
6
|
+
|
|
7
|
+
A Skill is a task procedure for a coding agent: how to do one integration or one kind of work with Nativele, step by step, with a complete example, the known limitations, and commands that prove it worked. It is not reference documentation: facts about the library's API live in `docs/`, and a Skill links to them instead of restating them.
|
|
8
|
+
|
|
9
|
+
An integration Skill (a framework, a build tool, a library) starts from a project that already uses that tool and teaches how to bring Nativele into it: what to add, what to change, and what to watch for. It does not teach how to set the tool itself up from nothing; the tool's own documentation and starter projects cover that.
|
|
10
|
+
|
|
11
|
+
A Skill never changes the library's runtime and never adds a dependency to the library's core. Third-party packages a Skill uses are dependencies of the user's project, installed by the Skill's own steps.
|
|
12
|
+
|
|
13
|
+
## Layout
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
skills/
|
|
17
|
+
├── README.md # this specification and the contribution guide
|
|
18
|
+
├── nativele-core/
|
|
19
|
+
│ └── SKILL.md
|
|
20
|
+
├── nativele-react/
|
|
21
|
+
│ ├── SKILL.md
|
|
22
|
+
│ └── example/ # Official only: the project the Skill is verified with
|
|
23
|
+
│ ├── package.json # exact versions of every tool the Skill names
|
|
24
|
+
│ ├── ... # the minimal app the SKILL.md example comes from
|
|
25
|
+
│ └── verify.mjs # the browser checks `npm run verify` ends with
|
|
26
|
+
└── ...
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The npm package ships every `SKILL.md` (so the CLI can list and read them offline), not the `example/` folders.
|
|
30
|
+
|
|
31
|
+
## SKILL.md format
|
|
32
|
+
|
|
33
|
+
`SKILL.md` follows the Agent Skills format (YAML frontmatter with `name` and `description`, then Markdown), so Claude Code and other agents that read skills can load it as it is. Nativele's own fields go under `metadata`:
|
|
34
|
+
|
|
35
|
+
```markdown
|
|
36
|
+
---
|
|
37
|
+
name: nativele-react
|
|
38
|
+
description: "Use Nativele web components inside a React 19 app: props, events, children and slots, lifecycle, and TSX types. Use when a project has react and @nativele/ts-dom."
|
|
39
|
+
metadata:
|
|
40
|
+
tier: official # official | community | experimental
|
|
41
|
+
nativele: ">=0.5.0 <0.6.0" # the library versions the Skill was verified with
|
|
42
|
+
verified: "2026-11-05" # date of the last verification
|
|
43
|
+
tools: "react@19.3.1 react-dom@19.3.1 vite@8.3.5 @vitejs/plugin-react@6.2.1 typescript@7.0.2"
|
|
44
|
+
author: "Nativele maintainers" # for community Skills: the contributor
|
|
45
|
+
detects: "react" # package.json dependencies that make ts-dom doctor recommend this Skill
|
|
46
|
+
---
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Quote the `description` and any value that contains `: `, so the frontmatter stays valid YAML for every agent that loads it.
|
|
50
|
+
|
|
51
|
+
Required sections, in this order:
|
|
52
|
+
|
|
53
|
+
1. **When to use**: the situation and the signals (dependencies, files) that call for this Skill, and when not to use it.
|
|
54
|
+
2. **Versions**: a table of every tool and the exact version it was verified with, and the date. If the project uses a different major version of a tool, the Skill says so and stops treating its steps as verified.
|
|
55
|
+
3. **Set-up**: numbered steps with the exact commands and file contents, starting from a working project of the tool (for an integration Skill) and covering only what Nativele adds or changes.
|
|
56
|
+
4. **Example**: a complete, minimal working example (every file it needs, short).
|
|
57
|
+
5. **Known limitations**: what does not work or needs care, each with what to do instead. An agent must never have to guess these.
|
|
58
|
+
6. **Verify**: the commands that prove the integration works in the user's project (for example `npx ts-dom check`, `npx tsc --noEmit`, the project's build, a test), with the output to expect.
|
|
59
|
+
7. **Troubleshooting**: symptoms seen while verifying, their cause and fix.
|
|
60
|
+
8. **See also**: links to the guides in `docs/` for the facts used.
|
|
61
|
+
|
|
62
|
+
Rules for the content:
|
|
63
|
+
|
|
64
|
+
- Only what was run: every step, example and limitation comes from the verified example, not from memory of how a tool used to work.
|
|
65
|
+
- Version-bound statements say so ("React 19 passes object props as properties; React 18 passes everything as attributes").
|
|
66
|
+
- No promises beyond the verified versions.
|
|
67
|
+
- English, plain and concrete; no restating of `docs/`.
|
|
68
|
+
|
|
69
|
+
## Tiers
|
|
70
|
+
|
|
71
|
+
| Tier | Who maintains it | Requirements | Shown by `ts-dom skills` |
|
|
72
|
+
|---|---|---|---|
|
|
73
|
+
| Official | the Nativele maintainers | accepted through an agent trial, and an `example/` project that `npm run verify` checks in CI; verified within the last 6 months | as official |
|
|
74
|
+
| Community | its author | the full SKILL.md format, `author` and `verified` filled in, accepted through a maintainer's agent trial | as community, with the author |
|
|
75
|
+
| Experimental | its author | the SKILL.md format; verification not complete | as experimental, with a warning |
|
|
76
|
+
|
|
77
|
+
- A new contribution starts as Experimental. It becomes Community when it passes a maintainer's agent trial (see "How a Skill pull request is accepted"), and Official when the maintainers adopt it with an `example/` project in CI.
|
|
78
|
+
- A Skill whose `verified` date is older than 6 months, or whose `nativele` range does not include the installed version, is shown as stale. A stale Official Skill whose CI check fails is moved to Experimental until fixed.
|
|
79
|
+
|
|
80
|
+
## Delivering Skills to projects
|
|
81
|
+
|
|
82
|
+
The library already lives in the user's `node_modules`, so its Skills travel with it:
|
|
83
|
+
|
|
84
|
+
- `ts-dom skills` lists the Skills shipped with the installed version: name, tier, description, verified date and versions, whether it is stale, and its path inside `node_modules`. `--json` prints the same as data for agents.
|
|
85
|
+
- `ts-dom skills <name>` prints that Skill (so an agent can read it without knowing the path).
|
|
86
|
+
- `ts-dom skills add <name...>` copies Skills into the project: into `.claude/skills/<name>/` when the project has a `.claude` folder, otherwise into `.agents/skills/<name>/` (`--to=<dir>` for another folder), where agents that load skills pick them up. It only copies; it never installs the third-party packages a Skill mentions.
|
|
87
|
+
- `ts-dom doctor` reads the project's `package.json` and, for each Skill whose `detects` dependency is present and that the project has no copy of, reports it as an `info` issue (`skill-available`); a copy that differs from the shipped one is reported as `skill-outdated`, for example: `package.json:1:1 info [skill-available] react is a dependency: the nativele-react Skill covers using Nativele components in React. Fix: run ts-dom skills add nativele-react, or read it with ts-dom skills nativele-react.` Info issues never change the exit code.
|
|
88
|
+
|
|
89
|
+
None of this adds a runtime dependency: listing and copying read the Markdown files with Node's file API, and the frontmatter is parsed with a small reader for the fields above.
|
|
90
|
+
|
|
91
|
+
## Verification of Official Skills
|
|
92
|
+
|
|
93
|
+
- `skills/<name>/example/package.json` pins every tool version the Skill names, depends on the library through `file:../../..` (the library in the same repository), and has `npm run verify`, which runs `ts-dom check`, `ts-dom doctor`, `tsc`, the build, and a short real-browser test of what the Skill promises.
|
|
94
|
+
- `nativele-core` has no example of its own: the library's CI, which checks every example in the repository, verifies it.
|
|
95
|
+
- The workflow `.github/workflows/skills.yml` runs `npm run verify` for each Official Skill when an `example/` changes, when the library's runtime changes, and once a month, so a third-party release that breaks a Skill is found. A change to `SKILL.md` runs only `.github/workflows/skills-format.yml`, which checks every `SKILL.md` against this specification. Neither is a required check for `main`.
|
|
96
|
+
- The monthly run opens nothing by itself; a failure is reported in the workflow run, and the Skill is fixed or moved to Experimental.
|
|
97
|
+
|
|
98
|
+
## Contributing a Skill
|
|
99
|
+
|
|
100
|
+
- Copy `skills/_template/SKILL.md`, fill in every section, set `tier: experimental`.
|
|
101
|
+
- Run the Skill's verification commands in a fresh project with the versions you list, and paste their output in the PR description.
|
|
102
|
+
- The PR touches only `skills/<name>/`; a PR that changes the library and adds a Skill is split. Community and Experimental Skills live in this repository, in the same `skills/` folder, so they ship with the package and `ts-dom skills` lists them with their tier.
|
|
103
|
+
|
|
104
|
+
## How a Skill pull request is accepted
|
|
105
|
+
|
|
106
|
+
A Skill is written for agents, so the maintainers accept it by having an agent use it: a fresh agent that knows nothing about the Skill does real work with nothing but the Skill, and the result is checked. Reading the Skill or rerunning its verification commands is not enough, because neither shows what the Skill leaves out.
|
|
107
|
+
|
|
108
|
+
1. **Check the format**: the frontmatter is complete, every required section is there, the facts link to `docs/` instead of restating them, and nothing would add a dependency to the library's core.
|
|
109
|
+
2. **Build the package from the PR branch** (`npm run build:lib && npm pack`), so the trial uses the real delivery path.
|
|
110
|
+
3. **Start a fresh project** in an empty folder outside the repository: for an integration Skill, the tool's own starter project at the versions the Skill lists (for example `create-vite` with the `react-ts` template), built once to confirm it works before Nativele is added. Install the packed library and the exact tool versions the Skill lists, and install the Skill the way users do: `npx ts-dom skills add <name>`.
|
|
111
|
+
4. **Give a fresh agent a task** that the Skill says it covers, written from its "When to use" and "Example": a small but real piece of work that goes through every set-up step and touches every known limitation. The agent may use the Skill, `npx ts-dom` and the public guides in `docs/`, but not the Nativele source, this repository or the Skill's `example/`. It reports the files it wrote, the output of every Verify command, every point where it had to guess or look elsewhere, and every error it hit and how it fixed it.
|
|
112
|
+
5. **Check the result independently**, without relying on the agent's report: run the Verify commands again and check the behavior in a real browser or with a test.
|
|
113
|
+
|
|
114
|
+
The Skill is accepted when:
|
|
115
|
+
|
|
116
|
+
- every Verify command gives the expected output, and the work does what the task asked;
|
|
117
|
+
- the agent followed the Skill's steps as written, and no step was wrong for the stated versions;
|
|
118
|
+
- every guess, outside search or unexplained error in the report is already covered by the Skill; a guess about the tool itself (how to configure the framework, the build tool or the test runner beyond what Nativele needs) is not a gap in the Skill.
|
|
119
|
+
|
|
120
|
+
Otherwise the maintainers send the gaps back to the contributor as review comments (a missing step, a limitation to list, a Troubleshooting row to add), and the trial runs again, with a new agent, on the updated Skill. The record of the passing trial (agent and model, date, versions, Verify output) is added to the PR as a review comment.
|
|
121
|
+
|
|
122
|
+
## Skills
|
|
123
|
+
|
|
124
|
+
| Skill | Tier | Status | Covers |
|
|
125
|
+
|---|---|---|---|
|
|
126
|
+
| `nativele-core` | Official | available | building with Nativele itself: the `ts-dom check` loop, components, lists, data flow |
|
|
127
|
+
| `nativele-react` | Official | available | Nativele components in React 19: props, events, children and slots, lifecycle, TSX types |
|
|
128
|
+
| `nativele-integration` | Experimental | available | Nativele components in a framework without its own Skill: defining before render, properties and attributes, events, slots, lifecycle, types |
|
|
129
|
+
| `nativele-vite` | Official | available | Nativele in a Vite app: the plugin and `root`, multi-page builds, the build script, hot updates, Vite+ |
|
|
130
|
+
| `nativele-vue` | Official | available | Nativele components in Vue 3: `isCustomElement`, props, events, `v-model`, slots, `<KeepAlive>`, template types |
|
|
131
|
+
| `nativele-svelte` | Experimental | available | Nativele components in Svelte 5: props, event attributes, slots, `bind:this`, template types |
|
|
132
|
+
| `nativele-angular` | Experimental | available | Nativele components in Angular: `CUSTOM_ELEMENTS_SCHEMA`, bindings, zoneless change detection, `ts-dom gen` in the build, content projection |
|
|
133
|
+
| `nativele-state-management` | Official | planned | `createStore`, and Zustand or Redux through `bind` |
|
|
134
|
+
| `nativele-testing` | Official | planned | Vitest and Playwright |
|
|
135
|
+
| Tailwind CSS, TanStack Query, Zod | Experimental or Community | open for contributions | |
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nativele-<tool>
|
|
3
|
+
description: "What the Skill lets an agent do, and when to use it (the dependencies or files that call for it)."
|
|
4
|
+
metadata:
|
|
5
|
+
tier: experimental
|
|
6
|
+
nativele: ">=0.5.0 <0.6.0"
|
|
7
|
+
verified: "YYYY-MM-DD"
|
|
8
|
+
tools: "<tool>@<exact version> <other tool>@<exact version>"
|
|
9
|
+
author: "<your name or GitHub handle>"
|
|
10
|
+
detects: "<package.json dependency names, space-separated>"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Nativele + <tool>
|
|
14
|
+
|
|
15
|
+
## When to use
|
|
16
|
+
|
|
17
|
+
- The situation, and the signals (dependencies, files) that call for this Skill.
|
|
18
|
+
- When not to use it.
|
|
19
|
+
|
|
20
|
+
## Versions
|
|
21
|
+
|
|
22
|
+
| Tool | Verified version |
|
|
23
|
+
|---|---|
|
|
24
|
+
| @nativele/ts-dom | x.y.z |
|
|
25
|
+
| <tool> | x.y.z |
|
|
26
|
+
|
|
27
|
+
Verified on YYYY-MM-DD. With a different major version of <tool>, treat every step as unverified.
|
|
28
|
+
|
|
29
|
+
## Set-up
|
|
30
|
+
|
|
31
|
+
1. Numbered steps with the exact commands and file contents.
|
|
32
|
+
|
|
33
|
+
## Example
|
|
34
|
+
|
|
35
|
+
The complete, minimal working example: every file it needs.
|
|
36
|
+
|
|
37
|
+
## Known limitations
|
|
38
|
+
|
|
39
|
+
1. What does not work or needs care, and what to do instead.
|
|
40
|
+
|
|
41
|
+
## Verify
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
npx ts-dom check # expect: ts-dom check: no issues
|
|
45
|
+
npx tsc --noEmit # expect: no errors
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
What to check in the browser or with a test.
|
|
49
|
+
|
|
50
|
+
## Troubleshooting
|
|
51
|
+
|
|
52
|
+
| Symptom | Cause | Fix |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
|
|
55
|
+
## See also
|
|
56
|
+
|
|
57
|
+
- Links to the guides in https://github.com/Pixmi/nativele/tree/main/docs for the facts used.
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nativele-angular
|
|
3
|
+
description: "Use Nativele web components inside an existing Angular 22 app with standalone components: CUSTOM_ELEMENTS_SCHEMA, property and event bindings, change detection, content projection into slots, lifecycle, and generating the templates for the Angular CLI build. Use when a project depends on @angular/core and @nativele/ts-dom."
|
|
4
|
+
metadata:
|
|
5
|
+
tier: experimental
|
|
6
|
+
nativele: ">=0.5.1 <0.6.0"
|
|
7
|
+
verified: "2026-11-05"
|
|
8
|
+
tools: "@angular/core@22.2.4 @angular/compiler-cli@22.2.4 @angular/build@22.2.6 @angular/cli@22.2.6 typescript@6.0.3 vitest@5.1.0 jsdom@30.1.3"
|
|
9
|
+
author: "Nativele maintainers"
|
|
10
|
+
detects: "@angular/core"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Nativele + Angular
|
|
14
|
+
|
|
15
|
+
## When to use
|
|
16
|
+
|
|
17
|
+
- The project is an Angular 22 app with standalone components (as made by `ng new`) and wants to use components written with Nativele (`@nativele/ts-dom`), for example widgets shared with non-Angular pages.
|
|
18
|
+
- Signals: `@angular/core` and `@nativele/ts-dom` in `package.json`, `angular.json`, Angular templates that use tags such as `<info-card>`.
|
|
19
|
+
- Not for writing the Nativele components themselves: use `nativele-core` (`npx ts-dom skills add nativele-core`).
|
|
20
|
+
- Not verified with NgModule-based apps, server-side rendering or hydration, or Angular versions other than 22.
|
|
21
|
+
|
|
22
|
+
Nativele components are native custom elements. Angular renders their tags like any other DOM element; nothing wraps them.
|
|
23
|
+
|
|
24
|
+
## Versions
|
|
25
|
+
|
|
26
|
+
| Tool | Verified version |
|
|
27
|
+
|---|---|
|
|
28
|
+
| @nativele/ts-dom | 0.5.1 |
|
|
29
|
+
| @angular/core, @angular/common, @angular/compiler, @angular/compiler-cli, @angular/platform-browser, @angular/router | 22.2.4 |
|
|
30
|
+
| @angular/cli, @angular/build | 22.2.6 |
|
|
31
|
+
| typescript | 6.0.3 |
|
|
32
|
+
| vitest (through `ng test`) | 5.1.0 |
|
|
33
|
+
| jsdom | 30.1.3 |
|
|
34
|
+
|
|
35
|
+
Verified on 2026-11-05 in Google Chrome 148, against `ng serve` (development and production configurations), in an app made with `npx @angular/cli@latest new <name> --defaults --skip-git`. That app is zoneless (no `zone.js`); the same checks also passed with `zone.js` 0.16.3 and `provideZoneChangeDetection()`. With a different major version of Angular, treat every step as unverified.
|
|
36
|
+
|
|
37
|
+
## Set-up
|
|
38
|
+
|
|
39
|
+
1. Keep the Nativele components in their own folder, for example `src/nv/`, each as `x.html` plus `x.ts`. Write them with the core Skill: `npx ts-dom skills add nativele-core`.
|
|
40
|
+
2. Install: `npm i @nativele/ts-dom`.
|
|
41
|
+
3. Set `"strict": true` in `compilerOptions` of the root `tsconfig.json` (the Angular 22 CLI template leaves it out; `tsconfig.app.json` and `tsconfig.spec.json` extend the root, so both get it). Nativele's type checks depend on it. If turning it on for the whole app is not possible yet, set it in `tsconfig.app.json` only; `ts-dom doctor` then still warns about `tsconfig.spec.json`.
|
|
42
|
+
4. Generate the template modules before every Angular command. The Angular CLI build (`@angular/build:application`) takes no Vite plugins from the project, so `tsDom()` cannot be added; run `ts-dom gen` from npm's `pre` scripts instead:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
npm pkg set scripts.prestart="ts-dom gen src/nv --whitespace=collapse" \
|
|
46
|
+
scripts.prebuild="ts-dom gen src/nv --whitespace=collapse" \
|
|
47
|
+
scripts.pretest="ts-dom gen src/nv --whitespace=collapse"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`npm start`, `npm run build` and `npm test` then generate first (`npx ng ...` called directly does not). `--whitespace=collapse` is there because the CLI template sets up Prettier, and `ts-dom doctor` asks for it; doctor finds the flag in the `package.json` scripts. Collapsing also drops the whitespace between tags, so `textContent` reads `x1A1:new`, not `x1 A1:new`; write test expectations that way.
|
|
51
|
+
|
|
52
|
+
While `npm start` runs, keep a second terminal with `npx ts-dom gen src/nv --watch --whitespace=collapse`: editing a template or adding a component regenerates its module, and `ng serve` rebuilds and reloads the page.
|
|
53
|
+
|
|
54
|
+
5. Pass the folder to every `ts-dom` command (`npx ts-dom check src/nv`). Without it the default is `src`, and `ts-dom` reads Angular's own templates (`src/app/*.html`) as Nativele templates and reports errors for them.
|
|
55
|
+
6. Add the generated files to `.gitignore`:
|
|
56
|
+
|
|
57
|
+
```gitignore
|
|
58
|
+
*.html.ts
|
|
59
|
+
ts-dom-tags.ts
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
7. Import every Nativele component module in `src/main.ts`, before `bootstrapApplication`:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
// src/main.ts
|
|
66
|
+
import "./nv/info-card.js";
|
|
67
|
+
import "./nv/shadow-card.js";
|
|
68
|
+
import "./nv/light-card.js";
|
|
69
|
+
import "./nv/live-badge.js";
|
|
70
|
+
import { bootstrapApplication } from '@angular/platform-browser';
|
|
71
|
+
import { appConfig } from './app/app.config';
|
|
72
|
+
import { App } from './app/app';
|
|
73
|
+
|
|
74
|
+
bootstrapApplication(App, appConfig)
|
|
75
|
+
.catch((err) => console.error(err));
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Angular sets `[prop]` bindings as properties even on an element that is not defined yet, and Nativele takes those values over when `define()` runs, so a late import loses no data; but until then the element is empty. Importing first makes the first render complete.
|
|
79
|
+
|
|
80
|
+
8. Add `schemas: [CUSTOM_ELEMENTS_SCHEMA]` to every standalone component whose template uses a Nativele tag. Without it the build fails with `NG8001: 'info-card' is not a known element` and `NG8002: Can't bind to 'item' since it isn't a known property of 'info-card'`.
|
|
81
|
+
|
|
82
|
+
## Example
|
|
83
|
+
|
|
84
|
+
The Nativele components, as in `nativele-core`:
|
|
85
|
+
|
|
86
|
+
```html
|
|
87
|
+
<!-- src/nv/info-card.html -->
|
|
88
|
+
<span>{{ label }}</span>
|
|
89
|
+
<b>{{ count }}</b>
|
|
90
|
+
<i data-ref="itemName"></i>
|
|
91
|
+
<button type="button" data-on-click="pick">Pick</button>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
// src/nv/info-card.ts
|
|
96
|
+
import { Component, define, event } from "@nativele/ts-dom";
|
|
97
|
+
import tpl from "./info-card.html.js";
|
|
98
|
+
|
|
99
|
+
export interface Item {
|
|
100
|
+
id: number;
|
|
101
|
+
name: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export class InfoCard extends Component(tpl, {
|
|
105
|
+
props: { label: "", count: 0, item: null as Item | null },
|
|
106
|
+
emits: { "item-select": event<{ id: number }>() },
|
|
107
|
+
}) {
|
|
108
|
+
pick(): void {
|
|
109
|
+
const item = this.props.item;
|
|
110
|
+
if (item) this.emit("item-select", { id: item.id });
|
|
111
|
+
}
|
|
112
|
+
override render(view: { item: Item | null }): void {
|
|
113
|
+
this.refs.itemName.textContent = view.item ? view.item.name : "-";
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
define("info-card", InfoCard);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```html
|
|
120
|
+
<!-- src/nv/shadow-card.html and src/nv/light-card.html -->
|
|
121
|
+
<h2><slot name="title">Untitled</slot></h2>
|
|
122
|
+
<div><slot></slot></div>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
// src/nv/shadow-card.ts
|
|
127
|
+
import { Component, define } from "@nativele/ts-dom";
|
|
128
|
+
import tpl from "./shadow-card.html.js";
|
|
129
|
+
|
|
130
|
+
export class ShadowCard extends Component(tpl, { shadow: true }) {}
|
|
131
|
+
define("shadow-card", ShadowCard);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
// src/nv/light-card.ts
|
|
136
|
+
import { Component, define } from "@nativele/ts-dom";
|
|
137
|
+
import tpl from "./light-card.html.js";
|
|
138
|
+
|
|
139
|
+
export class LightCard extends Component(tpl) {}
|
|
140
|
+
define("light-card", LightCard);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```html
|
|
144
|
+
<!-- src/nv/live-badge.html -->
|
|
145
|
+
<span>{{ user }}</span>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
// src/nv/session.ts
|
|
150
|
+
import { createStore } from "@nativele/ts-dom";
|
|
151
|
+
|
|
152
|
+
export const session = createStore({ user: "Ada" });
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
// src/nv/live-badge.ts
|
|
157
|
+
import { Component, define } from "@nativele/ts-dom";
|
|
158
|
+
import tpl from "./live-badge.html.js";
|
|
159
|
+
import { session } from "./session.js";
|
|
160
|
+
|
|
161
|
+
export class LiveBadge extends Component(tpl) {
|
|
162
|
+
override connected(): void {
|
|
163
|
+
this.bind(session, (s) => (this.text.user = s.user));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
define("live-badge", LiveBadge);
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The Angular component that uses them:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
// src/app/app.ts
|
|
173
|
+
import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';
|
|
174
|
+
import type { EmitEvent } from '@nativele/ts-dom';
|
|
175
|
+
import type { InfoCard, Item } from '../nv/info-card.js';
|
|
176
|
+
import { session } from '../nv/session.js';
|
|
177
|
+
|
|
178
|
+
const fruits: Item[] = [
|
|
179
|
+
{ id: 1, name: 'apple' },
|
|
180
|
+
{ id: 2, name: 'pear' },
|
|
181
|
+
{ id: 3, name: 'plum' },
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
@Component({
|
|
185
|
+
selector: 'app-root',
|
|
186
|
+
templateUrl: './app.html',
|
|
187
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
188
|
+
})
|
|
189
|
+
export class App {
|
|
190
|
+
protected readonly count = signal(0);
|
|
191
|
+
protected readonly item = signal<Item>(fruits[0]!);
|
|
192
|
+
protected readonly picked = signal<number[]>([]);
|
|
193
|
+
protected pickedPlain = '';
|
|
194
|
+
protected readonly fruits = signal(fruits);
|
|
195
|
+
protected readonly rows = signal(['a', 'b', 'c']);
|
|
196
|
+
protected readonly showTitle = signal(true);
|
|
197
|
+
protected readonly showBadge = signal(true);
|
|
198
|
+
|
|
199
|
+
select(e: Event): void {
|
|
200
|
+
const id = (e as EmitEvent<InfoCard, 'item-select'>).detail.id;
|
|
201
|
+
this.picked.update((p) => [...p, id]);
|
|
202
|
+
this.pickedPlain += id;
|
|
203
|
+
}
|
|
204
|
+
next(): void {
|
|
205
|
+
this.count.update((c) => c + 1);
|
|
206
|
+
this.item.set(fruits[1]!);
|
|
207
|
+
}
|
|
208
|
+
rotate(): void {
|
|
209
|
+
this.rows.update((r) => [r.at(-1)!, ...r.slice(0, -1)]);
|
|
210
|
+
this.fruits.update((f) => [f.at(-1)!, ...f.slice(0, -1)]);
|
|
211
|
+
}
|
|
212
|
+
toggleB(): void {
|
|
213
|
+
this.rows.update((r) => (r.includes('b') ? r.filter((x) => x !== 'b') : ['b', ...r]));
|
|
214
|
+
}
|
|
215
|
+
rename(): void {
|
|
216
|
+
session.set({ user: 'Grace' });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
```html
|
|
222
|
+
<!-- src/app/app.html -->
|
|
223
|
+
<info-card id="card" label="Fruit" [count]="count()" [item]="item()" (item-select)="select($event)" />
|
|
224
|
+
<p id="picked">{{ picked().join(',') }}</p>
|
|
225
|
+
<p id="picked-plain">{{ pickedPlain }}</p>
|
|
226
|
+
<button type="button" id="next" (click)="next()">Next</button>
|
|
227
|
+
|
|
228
|
+
<ul id="cards">
|
|
229
|
+
@for (f of fruits(); track f.id) {
|
|
230
|
+
<li><info-card [label]="f.name" [item]="f" (item-select)="select($event)" /><live-badge /></li>
|
|
231
|
+
}
|
|
232
|
+
</ul>
|
|
233
|
+
|
|
234
|
+
<shadow-card id="shadow">
|
|
235
|
+
@if (showTitle()) {
|
|
236
|
+
<span slot="title">Shadow</span>
|
|
237
|
+
}
|
|
238
|
+
@for (r of rows(); track r) {
|
|
239
|
+
<p>{{ r }}</p>
|
|
240
|
+
}
|
|
241
|
+
</shadow-card>
|
|
242
|
+
|
|
243
|
+
<light-card id="light">
|
|
244
|
+
<div slot="title">
|
|
245
|
+
@if (showTitle()) {
|
|
246
|
+
<span>Light</span>
|
|
247
|
+
}
|
|
248
|
+
</div>
|
|
249
|
+
<div>
|
|
250
|
+
@for (r of rows(); track r) {
|
|
251
|
+
<p>{{ r }}</p>
|
|
252
|
+
}
|
|
253
|
+
</div>
|
|
254
|
+
</light-card>
|
|
255
|
+
|
|
256
|
+
@if (showBadge()) {
|
|
257
|
+
<live-badge id="badge" />
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
<button type="button" id="rotate" (click)="rotate()">Rotate</button>
|
|
261
|
+
<button type="button" id="toggle-b" (click)="toggleB()">Toggle b</button>
|
|
262
|
+
<button type="button" id="toggle-title" (click)="showTitle.set(!showTitle())">Toggle title</button>
|
|
263
|
+
<button type="button" id="toggle-badge" (click)="showBadge.set(!showBadge())">Toggle badge</button>
|
|
264
|
+
<button type="button" id="rename" (click)="rename()">Rename</button>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
- `[item]`, `[count]` and `[label]` are set as properties; the object is the same object the signal holds. A plain attribute (`label="Fruit"`) also fills a string prop.
|
|
268
|
+
- `(item-select)` listens to the exact, hyphenated event name on the element. The handler receives the `CustomEvent`; read its data through `EmitEvent<InfoCard, 'item-select'>`.
|
|
269
|
+
- A signal change re-renders the Nativele component in the same change detection pass. Changing state inside an `(item-select)` handler needs no extra call, in a zoneless app too, even for a plain field such as `pickedPlain`, because Angular runs change detection after every template listener.
|
|
270
|
+
- `@for` with `track` moves `<info-card>` hosts when the list is reordered; they keep their props and listeners.
|
|
271
|
+
|
|
272
|
+
## Known limitations
|
|
273
|
+
|
|
274
|
+
1. **`CUSTOM_ELEMENTS_SCHEMA` turns off binding checks for unknown elements.** With it, Angular's compiler accepts any property on any hyphenated tag: `[item]="42"`, `[count]="'x'"` and `[nope]="1"` on `<info-card>` all build without an error. Angular has no mechanism to type-check bindings on custom elements, so keep the values typed in the Angular class (`signal<Item>(...)`) and check the Nativele side with `ts-dom check src/nv`.
|
|
275
|
+
2. **`$event` is typed `Event`.** Angular's template type checker types `$event` of a DOM event it does not know as `Event`, so `select($event.detail)` or a handler parameter typed `CustomEvent<...>` fails with `TS2339: Property 'detail' does not exist on type 'Event'` or `TS2345: Argument of type 'Event' is not assignable to parameter of type 'CustomEvent<...>'`. Take `Event` and cast it with `EmitEvent<Component, 'event-name'>` from `@nativele/ts-dom`; a wrong event name there becomes `never` and fails with `Property 'detail' does not exist on type 'never'`. Nothing checks that the cast names the element the listener is on.
|
|
276
|
+
3. **Bind properties, not attributes, for objects.** `[attr.item]="item()"` writes the attribute `item="[object Object]"` and the prop stays `null`. `[attr.count]` works for a number prop (the attribute text is converted), but `[count]` is simpler.
|
|
277
|
+
4. **Light DOM slots and Angular-managed children do not mix.** In the default Light DOM mode (`Component(tpl)` without `shadow`), Nativele moves the host's children into its slot positions. When `@if` or `@for` sit directly inside the tag, Angular still adds and removes nodes, but a node it re-inserts lands in the wrong place: a `<span slot="title">` brought back by `@if` ends up in the default slot while the title shows its fallback, and `assignedNodes()` stops matching the page. Angular reports no error. Use one of:
|
|
278
|
+
- `shadow: true` on the component; `@if` and `@for` children can then change freely;
|
|
279
|
+
- one stable element per slot that Angular never removes, with `@if`/`@for` inside it (the `light-card` above). A stable but empty wrapper still fills its slot, so the slot's fallback content does not show; when the fallback matters, use `shadow: true`.
|
|
280
|
+
5. **Zoneless rendering is scheduled.** In the CLI's default zoneless app, Angular updates the DOM after a click in a later task, so code that calls `button.click()` and reads the DOM right away sees the old values; with `zone.js` the DOM is already updated when `click()` returns. A test that reads the DOM right after dispatching an event must wait (Playwright: wait for the expected text; TestBed: `await fixture.whenStable()`). The Nativele component itself renders synchronously as soon as Angular sets the property.
|
|
281
|
+
6. **State changed outside Angular's listeners.** In a zoneless app, a plain field set from a Nativele store subscription (`session.subscribe(...)` in an Angular class) or a `setTimeout` does not re-render the Angular view; put the value in a signal. Nativele components that `bind` the store update themselves either way.
|
|
282
|
+
7. **`ng serve` started before the modules were generated stays broken.** It fails with `Could not resolve "./info-card.html.js"` and does not rebuild when `ts-dom gen` creates the files later. Use `npm start` (step 4 runs `gen` first), or restart `ng serve`, or save one of the component `.ts` files.
|
|
283
|
+
8. **Lifecycle.** `@if` removing a host runs `disconnected()` and ends its `bind` subscriptions; inserting it again runs `connected()` and subscribes again. `@for` reordering moves hosts, which runs `disconnected()` and `connected()` on them; `bind` keeps working after the move.
|
|
284
|
+
9. **Unit tests.** `main.ts` is not loaded by `ng test`: import the Nativele modules at the top of each spec that renders their tags. `tsconfig.spec.json` includes only `*.spec.ts` and `*.d.ts`, so `HTMLElementTagNameMap['info-card']` is not known there; import the class type (`import type { InfoCard } from '../nv/info-card.js'`). Nativele components render in `ng test`'s jsdom environment.
|
|
285
|
+
10. Not verified: NgModule-based apps, server-side rendering and hydration, Angular forms (`ngModel`, reactive forms) bound to Nativele components, `ng build --watch`, and Angular versions other than 22.
|
|
286
|
+
|
|
287
|
+
## Verify
|
|
288
|
+
|
|
289
|
+
Run in the project:
|
|
290
|
+
|
|
291
|
+
```sh
|
|
292
|
+
npx ts-dom gen src/nv --whitespace=collapse # writes the modules; prints nothing
|
|
293
|
+
npx ts-dom check src/nv --whitespace=collapse # expect: ts-dom check: no issues
|
|
294
|
+
npx ts-dom doctor src/nv # expect: ts-dom doctor: the set-up looks right (info lines are suggestions)
|
|
295
|
+
npx ngc -p tsconfig.app.json --noEmit # expect: no output, exit code 0
|
|
296
|
+
npm run build # expect: Application bundle generation complete.
|
|
297
|
+
npm test -- --watch=false # expect: the specs pass
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
- `ts-dom check` runs `tsc` on the app's TypeScript through the references of the root `tsconfig.json`, so it reports a type error in `src/app/app.ts` too. It does not read Angular templates. `ngc` (or the build) is what type-checks the Angular templates, including the `EmitEvent` casts.
|
|
301
|
+
- Run `ts-dom check` with `--whitespace=collapse` or after `gen`: when the generated files are missing, `check` writes them with whitespace kept as written; once they exist, it keeps the mode they were written with.
|
|
302
|
+
|
|
303
|
+
In the browser, against `npm start` (the development server shows Angular's warnings in the console), check:
|
|
304
|
+
|
|
305
|
+
- the element has no attribute for the object prop (`el.getAttributeNames()`) and `el.item` is the object;
|
|
306
|
+
- a click on the component's button runs the `(item-select)` handler once and updates the Angular view;
|
|
307
|
+
- after `@for` reorders hosts, each keeps its own `item`, and its events still fire;
|
|
308
|
+
- removing a host with `@if` ends its `bind` subscription: keep a reference to the removed element, change the store, and its text stays old;
|
|
309
|
+
- children that `@if`/`@for` toggle, reorder, add and remove stay in the right slot; read a slot with `el.assignedNodes(name)` (`""` is the default slot), which works in both modes, and compare elements, since Angular leaves comment anchors among the children;
|
|
310
|
+
- no errors or warnings in the console.
|
|
311
|
+
|
|
312
|
+
With Playwright, wait for each expected change instead of reading the DOM right after a click (limitation 5).
|
|
313
|
+
|
|
314
|
+
## Troubleshooting
|
|
315
|
+
|
|
316
|
+
| Symptom | Cause | Fix |
|
|
317
|
+
|---|---|---|
|
|
318
|
+
| `NG8001: 'info-card' is not a known element` | the standalone component has no `CUSTOM_ELEMENTS_SCHEMA` | add `schemas: [CUSTOM_ELEMENTS_SCHEMA]` to that component |
|
|
319
|
+
| `NG8002: Can't bind to 'item' since it isn't a known property of 'info-card'` | the same | the same |
|
|
320
|
+
| `TS2339: Property 'detail' does not exist on type 'Event'`, or `Argument of type 'Event' is not assignable to parameter of type 'CustomEvent<...>'` | Angular types `$event` of a custom event as `Event` | take `Event` in the handler and cast with `EmitEvent<Component, 'event-name'>` |
|
|
321
|
+
| `Property 'detail' does not exist on type 'never'` | `EmitEvent` names an event the component does not emit | use the exact name from the component's `emits` |
|
|
322
|
+
| `Could not resolve "./info-card.html.js"` from `ng build` or `ng serve` | the template modules were not generated | run `ts-dom gen src/nv --whitespace=collapse`; use `npm start`/`npm run build`, which run it first (step 4); restart a running `ng serve` |
|
|
323
|
+
| An object prop is `null`, and the element has `item="[object Object]"` | `[attr.item]` was used | bind the property: `[item]` |
|
|
324
|
+
| The element stays empty | its module is not imported | import it in `src/main.ts` (step 7) |
|
|
325
|
+
| A slotted child shows in the wrong slot or the slot fallback stays visible | `@if`/`@for` directly inside a Light DOM component with slots | `shadow: true`, or a stable element per slot |
|
|
326
|
+
| `ts-dom check` reports `invalid-hole` or `no-importer` in `src/app/app.html` | the command ran without a folder and read Angular templates | pass the Nativele folder: `ts-dom check src/nv` |
|
|
327
|
+
| `ts-dom doctor` warns `tsconfig-not-strict` | the Angular 22 CLI template does not set `strict` | step 3 |
|
|
328
|
+
| `ts-dom doctor` warns `formatter-whitespace` | Prettier is set up and no script generates with `--whitespace=collapse` | step 4 |
|
|
329
|
+
| `Property 'info-card' does not exist on type 'HTMLElementTagNameMap'` in a spec | `tsconfig.spec.json` does not include `ts-dom-tags.ts` | import the component class type instead |
|
|
330
|
+
| The Angular view does not update after a store change or a timer | zoneless change detection does not run for plain fields changed outside template listeners | keep that value in a signal |
|
|
331
|
+
|
|
332
|
+
## See also
|
|
333
|
+
|
|
334
|
+
- Components, props, events and `EmitEvent`: https://github.com/Pixmi/nativele/blob/main/docs/components.md#data-flow-props-down-events-up
|
|
335
|
+
- Slots, Light DOM and Shadow DOM: https://github.com/Pixmi/nativele/blob/main/docs/components.md#light-default-and-shadow
|
|
336
|
+
- Lifecycle and `bind`: https://github.com/Pixmi/nativele/blob/main/docs/components.md#components
|
|
337
|
+
- Stores: https://github.com/Pixmi/nativele/blob/main/docs/components.md#state-store-when-not-using-state
|
|
338
|
+
- Setup, `gen --watch`, whitespace and checks: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md
|
|
339
|
+
- A framework integration with typed templates: `npx ts-dom skills nativele-react`
|