@nativele/ts-dom 0.5.0 → 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 +23 -0
- package/README.md +5 -0
- package/README.zh-TW.md +5 -0
- package/dist/{a11y-wWL9rie7.mjs → a11y-CV3nBgBn.mjs} +1 -1
- package/dist/cli.mjs +38 -16
- package/dist/{lint-5vo_ifIc.mjs → lint-DfUAh7t3.mjs} +1 -1
- package/dist/oxlint-plugin.mjs +2 -2
- package/dist/vite-plugin.mjs +1 -1
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/skills/README.md +31 -10
- package/skills/nativele-angular/SKILL.md +339 -0
- package/skills/nativele-core/SKILL.md +92 -7
- package/skills/nativele-integration/SKILL.md +125 -0
- package/skills/nativele-react/SKILL.md +30 -14
- package/skills/nativele-svelte/SKILL.md +259 -0
- package/skills/nativele-vite/SKILL.md +197 -0
- package/skills/nativele-vue/SKILL.md +279 -0
|
@@ -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`
|
|
@@ -30,10 +30,92 @@ Verified on 2026-11-05 by the library's own CI, which type-checks, lints and run
|
|
|
30
30
|
|
|
31
31
|
## Set-up
|
|
32
32
|
|
|
33
|
-
1. `npm i @nativele/ts-dom` and `npm i -D typescript
|
|
34
|
-
2.
|
|
35
|
-
3.
|
|
36
|
-
4.
|
|
33
|
+
1. `npm i @nativele/ts-dom` and `npm i -D typescript`, and set `"type": "module"` in `package.json` (with or without a bundler).
|
|
34
|
+
2. Put the components under `src/`, the folder every `ts-dom` command scans by default; for another folder, pass it to each command (`npx ts-dom check web/ui`).
|
|
35
|
+
3. `tsconfig.json` with `"strict": true`, `"lib"` including `"DOM"`, and `"moduleResolution"` `"NodeNext"` or `"bundler"`; its `include` must cover the folders with templates. A complete one for the no-bundler case is in the setup guide: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md#installation-and-setup
|
|
36
|
+
4. With Vite, add `tsDom()` from `@nativele/ts-dom/vite` to `plugins`. Without a bundler, `ts-dom gen` plus `tsc` compiles the code, and the page needs an import map for `@nativele/ts-dom` plus a `<script type="module">` for the compiled entry, a module that imports the top-level components: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md#running-in-the-browser-no-bundler
|
|
37
|
+
5. Add `*.html.ts` and `ts-dom-tags.ts` to `.gitignore`: they are generated.
|
|
38
|
+
6. Run `npx ts-dom doctor` once; fix every error and warning it reports. Its `info` lines name the other Skills that fit the project.
|
|
39
|
+
|
|
40
|
+
## Example
|
|
41
|
+
|
|
42
|
+
A list whose items are components, with a store, a derived value and events from the items:
|
|
43
|
+
|
|
44
|
+
```html
|
|
45
|
+
<!-- src/todo-item.html -->
|
|
46
|
+
<input type="checkbox" data-ref="check" data-on-change="onToggle" />
|
|
47
|
+
<span>{{ text }}</span>
|
|
48
|
+
<button type="button" data-on-click="onRemove">remove</button>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
// src/todo-item.ts
|
|
53
|
+
import { Component, define, event } from "@nativele/ts-dom";
|
|
54
|
+
import tpl from "./todo-item.html.js";
|
|
55
|
+
import type { Todo } from "./store.js";
|
|
56
|
+
|
|
57
|
+
export class TodoItem extends Component(tpl, {
|
|
58
|
+
props: { todo: { id: 0, title: "", done: false } as Todo },
|
|
59
|
+
derive: { text: (v) => v.todo.title },
|
|
60
|
+
emits: { toggled: event(), removed: event() },
|
|
61
|
+
}) {
|
|
62
|
+
onToggle(): void {
|
|
63
|
+
this.emit("toggled");
|
|
64
|
+
}
|
|
65
|
+
onRemove(): void {
|
|
66
|
+
this.emit("removed");
|
|
67
|
+
}
|
|
68
|
+
override render(view: { todo: Todo }, changed: ReadonlySet<"todo">): void {
|
|
69
|
+
if (changed.has("todo")) this.refs.check.checked = view.todo.done;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
define("todo-item", TodoItem);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```html
|
|
76
|
+
<!-- src/todo-app.html -->
|
|
77
|
+
<p data-if="empty">Nothing to do</p>
|
|
78
|
+
<ul>
|
|
79
|
+
<template data-each="items" data-key="id" data-prop="todo">
|
|
80
|
+
<todo-item role="listitem" data-on-toggled="toggle" data-on-removed="drop"></todo-item>
|
|
81
|
+
</template>
|
|
82
|
+
</ul>
|
|
83
|
+
<p>{{ remaining }} left</p>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
// src/todo-app.ts
|
|
88
|
+
import { Component, define, type EventHit } from "@nativele/ts-dom";
|
|
89
|
+
import tpl from "./todo-app.html.js";
|
|
90
|
+
import { todos, type Todo } from "./store.js"; // export const todos = createStore<readonly Todo[]>([]);
|
|
91
|
+
import "./todo-item.js";
|
|
92
|
+
|
|
93
|
+
export class TodoApp extends Component(tpl, {
|
|
94
|
+
state: { items: [] as readonly Todo[] },
|
|
95
|
+
derive: {
|
|
96
|
+
remaining: (v) => v.items.filter((t) => !t.done).length,
|
|
97
|
+
empty: (v) => v.items.length === 0,
|
|
98
|
+
},
|
|
99
|
+
}) {
|
|
100
|
+
override connected(): void {
|
|
101
|
+
this.bind(todos, (items) => this.set({ items }));
|
|
102
|
+
}
|
|
103
|
+
toggle(_e: Event, hit: EventHit<Todo, "id">): void {
|
|
104
|
+
todos.set((s) => s.map((t) => (t.id === hit.key ? { ...t, done: !t.done } : t)));
|
|
105
|
+
}
|
|
106
|
+
drop(_e: Event, hit: EventHit<Todo, "id">): void {
|
|
107
|
+
todos.set((s) => s.filter((t) => t.id !== hit.key));
|
|
108
|
+
}
|
|
109
|
+
override render(view: { items: readonly Todo[] }, changed: ReadonlySet<"items">): void {
|
|
110
|
+
if (changed.has("items")) this.lists.items.items = view.items;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
define("todo-app", TodoApp);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- A `derive` function takes one argument: the state and props merged into one read-only object (other derived values are not in it).
|
|
117
|
+
- `emits` declares each event with `event()`, or `event<Detail>()` when it carries data; send the data with `this.emit("name", detail)`. The parent listens with `data-on-<event>` on the child tag and reads the data as `(e as EmitEvent<Child, "name">).detail`, because an HTML attribute cannot carry the event's type.
|
|
118
|
+
- `bind` goes in `connected()`; the subscription is released when the element is removed.
|
|
37
119
|
|
|
38
120
|
## The loop
|
|
39
121
|
|
|
@@ -47,14 +129,15 @@ Verified on 2026-11-05 by the library's own CI, which type-checks, lints and run
|
|
|
47
129
|
|
|
48
130
|
- No expressions in HTML: `{{ name }}` is a single identifier. Compute in `derive` (or in TypeScript) and fill a named hole.
|
|
49
131
|
- Change state only with `this.set()`. Rendering is synchronous; `this.batch()` combines updates.
|
|
50
|
-
- Once a component has `state`, `props` or `derive`, every `{{ hole }}` and `data-if` must name one of them. Show store data by copying it into state in the `bind` callback.
|
|
132
|
+
- Once a component has `state`, `props` or `derive`, every `{{ hole }}` and `data-if` outside a `<template data-each>` must name one of them (holes inside a plain-element list are filled by `replace`). Show store data by copying it into state in the `bind` callback.
|
|
51
133
|
- `store.set` replaces the whole value; a component's `this.set` merges. Spread the rest yourself: `store.set((s) => ({ ...s, status: "loading" }))`.
|
|
52
134
|
- Lists, attributes and focus go in `render(view, changed)`. `changed` holds the state, prop and derived names that changed; annotate it with the names you check (`changed: ReadonlySet<"items" | "visible">`), never `ReadonlySet<string>`.
|
|
53
135
|
- Props go down (the parent sets them), events go up (`emit`, handled with `data-on-<event>` on the child tag). `emit` does not bubble; data needed several levels away goes in a shared `createStore` that the components `bind`.
|
|
54
136
|
- State, props and the view are deeply read-only: replace objects instead of changing them (`{ ...todo, done: true }`).
|
|
55
137
|
- A `data-each` list cannot be nested: the inner list belongs to its own item component.
|
|
56
|
-
-
|
|
57
|
-
-
|
|
138
|
+
- A list of plain elements (no item component) has neither `data-key` nor `data-prop`; fill it with `this.lists.x.replace(items, key, fill)` in `render`: https://github.com/Pixmi/nativele/blob/main/docs/lists.md
|
|
139
|
+
- For a list of components, write `data-key` and `data-prop` on the `<template data-each>`, give the item component `role="listitem"` inside `<ul>` or `<ol>`, assign `this.lists.x.items = ...` in `render`, and type item handlers as `hit: EventHit<Item, "id">`. Each item component gets its item through that one prop; to pass more (selected, leading), map the items to objects that carry it before assigning.
|
|
140
|
+
- Do not name a handler or prop like a DOM or Nativele member (`remove`, `click`, `title`, `text`, `refs`, `state`, `render`, ...), or an emitted event like a native event (`click`, `change`, `input`, ...); `ts-dom check` reports both.
|
|
58
141
|
|
|
59
142
|
## Known limitations
|
|
60
143
|
|
|
@@ -77,6 +160,8 @@ Then exercise the changed component in a real browser (the project's own test ru
|
|
|
77
160
|
|
|
78
161
|
| Symptom | Cause | Fix |
|
|
79
162
|
|---|---|---|
|
|
163
|
+
| `ReferenceError: exports is not defined` in the browser | `package.json` has no `"type": "module"`, so `tsc` emitted CommonJS | set `"type": "module"`, delete the output folder, run `tsc` again |
|
|
164
|
+
| `Failed to resolve module specifier "@nativele/ts-dom"` | no bundler and no import map | add the import map from the setup guide before the module script |
|
|
80
165
|
| `Cannot find module './x.html.js'` | the generated file is missing | run `npx ts-dom check` (or `ts-dom gen`) |
|
|
81
166
|
| `missing-binding` at a template line | a hole or `data-if` name that state, props and derive do not have | add the field, or rename the hole |
|
|
82
167
|
| A list stays empty | `render` never assigns it, or guards it with a name that is not in `changed` | assign `this.lists.x.items` in `render`, guarded by the name of what it shows |
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nativele-integration
|
|
3
|
+
description: "Use Nativele web components inside an app built with a UI framework that has no Nativele Skill of its own (Preact, Solid, Lit, ...; React, Vue, Svelte and Angular have their own): defining before render, props as properties, listening to events, children and slots, lifecycle, and template types. Use when a project depends on @nativele/ts-dom and on such a framework."
|
|
4
|
+
metadata:
|
|
5
|
+
tier: experimental
|
|
6
|
+
nativele: ">=0.5.1 <0.6.0"
|
|
7
|
+
verified: "2026-11-05"
|
|
8
|
+
tools: "vue@3.5.44 vite@8.3.6 @vitejs/plugin-vue@6.0.10 vue-tsc@3.3.14 typescript@6.0.3"
|
|
9
|
+
author: "Nativele maintainers"
|
|
10
|
+
detects: "preact solid-js"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Nativele in a framework app
|
|
14
|
+
|
|
15
|
+
## When to use
|
|
16
|
+
|
|
17
|
+
- The app is built with a UI framework and wants to use components written with Nativele (`@nativele/ts-dom`), and no Skill exists for that framework yet. React, Vue, Svelte and Angular have their own (`nativele-react`, `nativele-vue`, `nativele-svelte`, `nativele-angular`); `npx ts-dom skills` lists every Skill.
|
|
18
|
+
- It is a procedure for finding out how the framework treats custom elements and connecting it correctly. It does not cover setting the framework itself up: start from the app as it is, with its own type checker and build.
|
|
19
|
+
- A framework with its own file format type-checks it with its own tool (Vue: `vue-tsc`). `ts-dom check` runs `vue-tsc` instead of `tsc` when the project's `package.json` lists it; for other formats see Troubleshooting.
|
|
20
|
+
- Not for writing the Nativele components themselves: use `nativele-core` (`npx ts-dom skills add nativele-core`).
|
|
21
|
+
|
|
22
|
+
Nativele components are native custom elements. The framework renders their tags like any other tag; nothing wraps them. Every rule below follows from that.
|
|
23
|
+
|
|
24
|
+
## Versions
|
|
25
|
+
|
|
26
|
+
| Tool | Verified version |
|
|
27
|
+
|---|---|
|
|
28
|
+
| @nativele/ts-dom | 0.5.1 |
|
|
29
|
+
| vue | 3.5.44 |
|
|
30
|
+
| vite | 8.3.6 |
|
|
31
|
+
| @vitejs/plugin-vue | 6.0.10 |
|
|
32
|
+
| vue-tsc | 3.3.14 |
|
|
33
|
+
| typescript | 6.0.3 |
|
|
34
|
+
|
|
35
|
+
Verified on 2026-11-05 by an agent that followed this Skill in a fresh Vue app made with `create-vite` (`vue-ts`), and checked in Google Chrome. The rules come from the custom element standard and hold for any framework, but each framework's own behavior (step 3) is verified only for the frameworks named here and in `nativele-react`.
|
|
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`, written with the core Skill.
|
|
40
|
+
2. Connect the build: with Vite, add `tsDom({ dirs: ["src/nv"] })` from `@nativele/ts-dom/vite` to `plugins`, and pass the same folder to every `ts-dom` command (`npx ts-dom check src/nv`). In the tsconfig whose `include` covers that folder, set `"strict": true`. If the project lints with oxlint, add `{ "name": "ts-dom", "specifier": "@nativele/ts-dom/oxlint" }` to `jsPlugins` and `"ts-dom/unused-markup": "error"` to `rules`. Add `*.html.ts` and `ts-dom-tags.ts` to `.gitignore`. Run `npx ts-dom doctor src/nv` and fix what it reports.
|
|
41
|
+
3. Find out, from the framework's documentation on custom elements (often called "web components"), these four things before writing any template:
|
|
42
|
+
|
|
43
|
+
| Question | Why it matters |
|
|
44
|
+
|---|---|
|
|
45
|
+
| Does the compiler need to be told that hyphenated tags are custom elements? | Otherwise it may try to resolve them as framework components and warn or fail. |
|
|
46
|
+
| Does a binding set a JS property or an HTML attribute on a custom element, and how do you force a property? | Objects and arrays reach a Nativele component only as properties. |
|
|
47
|
+
| How do you listen to a DOM event with an exact, hyphenated name? | Nativele events use names like `item-select`, and some syntaxes change the case. |
|
|
48
|
+
| Does the framework add, move and remove the children it renders inside the tag? | Light DOM slots move those children (limitation 3). |
|
|
49
|
+
|
|
50
|
+
4. Import every Nativele component module in the app's entry file, before the framework mounts anything:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
// src/main.ts
|
|
54
|
+
import "./nv/info-card.js";
|
|
55
|
+
// then create and mount the app
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Many frameworks decide between property and attribute by checking whether the element already has the property, which is only true after `define()` has run.
|
|
59
|
+
|
|
60
|
+
5. Give the framework's template type checker the Nativele tags. `ts-dom gen` writes `ts-dom-tags.ts`, which adds every exported component to `HTMLElementTagNameMap`; a component's props are the keys of its `props` member, and its events are in the optional `"~emits"` member (event name to `detail` type). Build the framework's tag declaration from these, so new components need no edit; `nativele-react` has the TSX one and `nativele-vue` the Vue one. Check how the framework names an event listener in its types: Vue types `@user-select` as the prop `onUserSelect`. If the framework has no typed templates, skip this step.
|
|
61
|
+
|
|
62
|
+
## Example
|
|
63
|
+
|
|
64
|
+
What every framework does underneath, in plain DOM calls. Check the framework's output against it:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import "./nv/info-card.js"; // define() runs first
|
|
68
|
+
|
|
69
|
+
const card = document.createElement("info-card");
|
|
70
|
+
card.label = "Ada"; // a string prop: property or attribute label="Ada"
|
|
71
|
+
card.item = { id: 1, name: "Ada" }; // an object prop: property only
|
|
72
|
+
card.addEventListener("item-select", (e) => { // the exact event name, on the element itself
|
|
73
|
+
console.log(e.detail.id);
|
|
74
|
+
});
|
|
75
|
+
host.append(card); // connected() runs here
|
|
76
|
+
card.item = { id: 2, name: "Grace" }; // a new object re-renders; changing item.name in place does not
|
|
77
|
+
card.remove(); // disconnected() runs, bind() subscriptions end
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
In a framework, the same calls come from its template syntax. `nativele-vue` (`npx ts-dom skills nativele-vue`) is a complete worked example: its compiler option, property binding, event listener and template type declaration answer the four questions of step 3 for Vue.
|
|
81
|
+
|
|
82
|
+
## Known limitations
|
|
83
|
+
|
|
84
|
+
1. **Define before render.** Import the component modules before the framework mounts (step 4). A framework that sees an undefined element may write an attribute instead of a property, and an object then arrives as the text `"[object Object]"`. A property set before `define()` is kept: Nativele takes it over when the element is upgraded.
|
|
85
|
+
2. **Props.** A prop whose default is a string, number or boolean can also come from an attribute, with camelCase names in kebab-case (`maxLength` from `max-length`, a boolean `true` when the attribute is present). Arrays and objects can only be set as properties. Pass a new object when data changes: props are compared by reference.
|
|
86
|
+
3. **Light DOM slots and framework-managed children do not mix.** In the default Light DOM mode (`Component(tpl)` without `shadow`), Nativele moves the tag's children into its slot positions; a framework that later inserts, removes or reorders those children can fail or leave nodes outside the slot. Use one of:
|
|
87
|
+
- `shadow: true` on the component, so the browser assigns slots natively and the children can change freely;
|
|
88
|
+
- one stable element per slot that the framework never removes, with the changing content inside it (`<div slot="title">...</div>`, `<div>...</div>` for the default slot). 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`.
|
|
89
|
+
4. **Events do not bubble.** `emit` dispatches a `CustomEvent` on the component that does not bubble, so the listener must be on that element, not on an ancestor. The event data is `event.detail`.
|
|
90
|
+
5. **Lifecycle.** `connected()` runs each time the element is inserted and `disconnected()` each time it is removed; a framework that moves an element (reordering a keyed list) runs both. `bind` subscriptions end on removal and start again on insertion, and the children already in a Light DOM component's slots move with it.
|
|
91
|
+
6. **The framework's templates are checked by the framework's type checker**, with the declaration from step 5, not by the Nativele rules. How much it checks depends on the framework: `vue-tsc` reports a wrong prop type or event detail, but not an unknown prop name.
|
|
92
|
+
|
|
93
|
+
## Verify
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
npx ts-dom check src/nv # expect: ts-dom check: no issues
|
|
97
|
+
npx ts-dom doctor src/nv # expect: ts-dom doctor: the set-up looks right (info lines are suggestions)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Then run the app's own type check and build (for example `npm run build` when it runs the framework's type checker). Keep one wrong prop per component kind that has props in a file the app never imports, under the framework's expect-error comment (a template comment such as `<!-- @ts-expect-error -->` is often not honored; check that a wrong prop fails without it), so the declaration is proved to type props.
|
|
101
|
+
|
|
102
|
+
In the browser, render a component with an object prop and an event listener, and check:
|
|
103
|
+
|
|
104
|
+
- the element has no attribute for the object prop (`el.getAttributeNames()`) and `el.<prop>` is the object;
|
|
105
|
+
- the listener runs once per event;
|
|
106
|
+
- children that the framework toggles, reorders, adds and removes stay in the right slot, with no errors or framework warnings in the console (run this check once against the dev server too: production builds drop framework warnings). Read a slot with `el.assignedNodes(name)` (`""` is the default slot), which works in both modes; a Light DOM component has no `<slot>` elements once rendered. Frameworks leave comment and empty text nodes as anchors among the children, so compare elements, not all nodes.
|
|
107
|
+
|
|
108
|
+
## Troubleshooting
|
|
109
|
+
|
|
110
|
+
| Symptom | Cause | Fix |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| An object prop shows `[object Object]`, or the element has an attribute holding it | the framework set an attribute: the element was not defined yet, or the binding syntax sets attributes | import the module before mounting; use the framework's property binding |
|
|
113
|
+
| The framework warns that it cannot resolve the component `info-card` | its compiler treats the tag as a framework component | mark hyphenated tags as custom elements in the framework's compiler options |
|
|
114
|
+
| The event listener never runs | the name was changed (camelCase), or the listener is on an ancestor | listen with the exact name, on the element that emits |
|
|
115
|
+
| Children vanish, appear outside the slot, or the framework throws during an update | framework-managed children inside a Light DOM component with slots | `shadow: true`, or a stable element per slot |
|
|
116
|
+
| `ts-dom check` reports `TS2307: Cannot find module` for a framework file (`./App.vue`, ...) | plain `tsc` cannot read the framework's files | Vue: keep `vue-tsc` in `package.json` (Vue's own type checker; `ts-dom check` uses it when listed). Other formats: pass `--project=` a tsconfig whose `include` covers only the Nativele folder, and type-check the rest with the framework's tool |
|
|
117
|
+
| The framework's type checker does not know the tag | no declaration built from `ts-dom-tags.ts`, or the component class is not exported | add the declaration (step 5), export the class, run `ts-dom gen` |
|
|
118
|
+
|
|
119
|
+
## See also
|
|
120
|
+
|
|
121
|
+
- Props, events and data flow: https://github.com/Pixmi/nativele/blob/main/docs/components.md#data-flow-props-down-events-up
|
|
122
|
+
- Slots, Light DOM and Shadow DOM: https://github.com/Pixmi/nativele/blob/main/docs/components.md#light-default-and-shadow
|
|
123
|
+
- Lifecycle: https://github.com/Pixmi/nativele/blob/main/docs/components.md#components
|
|
124
|
+
- Setup and checks: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md
|
|
125
|
+
- A complete framework integration: `npx ts-dom skills nativele-react`
|