@nativele/ts-dom 0.5.0 → 0.5.2
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 +56 -0
- package/README.md +10 -0
- package/README.zh-TW.md +10 -0
- package/dist/{a11y-wWL9rie7.mjs → a11y-CV3nBgBn.mjs} +1 -1
- package/dist/cli.mjs +38 -16
- package/dist/index.mjs +3 -3
- package/dist/{lint-5vo_ifIc.mjs → lint-Bwytj7L8.mjs} +2 -2
- 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 +36 -12
- package/skills/nativele-angular/SKILL.md +339 -0
- package/skills/nativele-core/SKILL.md +92 -7
- package/skills/nativele-integration/SKILL.md +195 -0
- package/skills/nativele-react/SKILL.md +30 -14
- package/skills/nativele-state-management/SKILL.md +319 -0
- package/skills/nativele-svelte/SKILL.md +259 -0
- package/skills/nativele-tailwind/SKILL.md +220 -0
- package/skills/nativele-tanstack-query/SKILL.md +302 -0
- package/skills/nativele-testing/SKILL.md +364 -0
- package/skills/nativele-vite/SKILL.md +197 -0
- package/skills/nativele-vue/SKILL.md +279 -0
- package/skills/nativele-zod/SKILL.md +345 -0
|
@@ -0,0 +1,195 @@
|
|
|
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: official
|
|
6
|
+
nativele: ">=0.5.1 <0.6.0"
|
|
7
|
+
verified: "2026-11-05"
|
|
8
|
+
tools: "preact@11.1.1 vite@8.3.6 @preact/preset-vite@2.10.6 typescript@7.0.2"
|
|
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. A framework that uses TSX (Preact, Solid) is checked by plain `tsc`, and `ts-dom check` reports its errors too.
|
|
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
|
+
| preact | 11.1.1 |
|
|
30
|
+
| vite | 8.3.6 |
|
|
31
|
+
| @preact/preset-vite | 2.10.6 |
|
|
32
|
+
| typescript | 7.0.2 |
|
|
33
|
+
|
|
34
|
+
Verified on 2026-11-05 in Google Chrome 148, starting from `create-vite` 9.3.4 with the `preact-ts` template, with the example project in the Nativele repository (`skills/nativele-integration/example`), which CI runs again when the example or the library changes and once a month. The procedure was also followed earlier in a Vue app made with the `vue-ts` template. The rules come from the custom element standard and hold for any framework, but each framework's own behavior (the answers to step 3) is verified only for Preact 11 here and for the frameworks that have their own Skill. With Preact 10, treat the Preact answers below as unverified.
|
|
35
|
+
|
|
36
|
+
## Set-up
|
|
37
|
+
|
|
38
|
+
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.
|
|
39
|
+
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` (in Vite's templates that is `tsconfig.app.json`; some starters, such as `preact-ts`, leave it off). 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.
|
|
40
|
+
3. Find out, from the framework's documentation on custom elements (often called "web components"), these four things before writing any template:
|
|
41
|
+
|
|
42
|
+
| Question | Why it matters |
|
|
43
|
+
|---|---|
|
|
44
|
+
| 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. |
|
|
45
|
+
| 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. |
|
|
46
|
+
| 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. |
|
|
47
|
+
| Does the framework add, move and remove the children it renders inside the tag? | Light DOM slots move those children (limitation 3). |
|
|
48
|
+
|
|
49
|
+
4. Import every Nativele component module in the app's entry file, before the framework mounts anything:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
// src/main.tsx
|
|
53
|
+
import "./nv/info-card.js";
|
|
54
|
+
// then create and mount the app
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Many frameworks decide between property and attribute by checking whether the element already has the property, which is only true after `define()` has run.
|
|
58
|
+
|
|
59
|
+
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; "In Preact (verified)" below has the one for Preact's JSX, `nativele-react` the one for React and `nativele-vue` the Vue one. Check how the framework names an event listener in its types: Preact and React type `item-select` as the prop `onitem-select`, Vue types `@item-select` as `onItemSelect`. If the framework has no typed templates, skip this step.
|
|
60
|
+
6. The generated files are not committed, so on a clean checkout a build script that runs the type checker before the bundler (the `preact-ts` template's `"build": "tsc -b && vite build"`) fails with `TS2307: Cannot find module './x.html.js'`. Put `ts-dom gen src/nv &&` in front: `"build": "ts-dom gen src/nv && tsc -b && vite build"`. The dev server needs no change: the Vite plugin generates the files when it starts.
|
|
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. Preact below is the worked example for a TSX framework; `nativele-vue` (`npx ts-dom skills nativele-vue`) is a complete one for a framework with its own template compiler. Both answer the four questions of step 3.
|
|
81
|
+
|
|
82
|
+
### In Preact (verified)
|
|
83
|
+
|
|
84
|
+
| Question | Preact 11 |
|
|
85
|
+
|---|---|
|
|
86
|
+
| Compiler option for custom elements | none: a lowercase JSX tag is always a DOM element |
|
|
87
|
+
| Property or attribute | a property when the element already has one of that name (`name in el`), otherwise an attribute; there is no syntax to force a property. Define before render (step 4); for a module loaded later, set object props from a `ref` (limitation 1) |
|
|
88
|
+
| Event listener | `on` plus the exact event name: `onitem-select={(e) => ...}`. Preact lowercases the whole name when the letter after `on` is uppercase, so `onItemSelect` listens to `itemselect` and never runs |
|
|
89
|
+
| Framework-managed children | yes: Preact inserts, removes and reorders them (limitation 3); keyed lists move the same element, with `moveBefore` where the browser has it (limitation 5) |
|
|
90
|
+
|
|
91
|
+
The JSX declaration, as `src/nativele-preact.d.ts` (the template's `tsconfig.app.json` includes `src`):
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
// src/nativele-preact.d.ts
|
|
95
|
+
import type { HTMLAttributes } from "preact";
|
|
96
|
+
|
|
97
|
+
type NativeleTag = Extract<keyof HTMLElementTagNameMap, `${string}-${string}`>;
|
|
98
|
+
type NativeleProps<E> = E extends { readonly props: infer P } ? { [K in keyof P & keyof E]?: E[K] } : unknown;
|
|
99
|
+
type NativeleEvents<E> = E extends { readonly "~emits"?: infer M }
|
|
100
|
+
? { [K in keyof NonNullable<M> & string as `on${K}`]?: (event: CustomEvent<NonNullable<M>[K]>) => void }
|
|
101
|
+
: unknown;
|
|
102
|
+
type NativeleElementProps<E extends HTMLElement> = Omit<HTMLAttributes<E>, keyof NativeleProps<E>> & NativeleProps<E> & NativeleEvents<E>;
|
|
103
|
+
type NativeleIntrinsics = { [K in NativeleTag]: NativeleElementProps<HTMLElementTagNameMap[K]> };
|
|
104
|
+
|
|
105
|
+
declare module "preact" {
|
|
106
|
+
namespace JSX {
|
|
107
|
+
interface IntrinsicElements extends NativeleIntrinsics {}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Used from a Preact component, with the `nv-info-card` and `nv-text-field` components of `nativele-vue`'s example (props `label`, `count`, `item`, event `item-select`; prop `value`, event `value-change`):
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
import { useState } from "preact/hooks";
|
|
116
|
+
import type { Item } from "./nv/info-card.js";
|
|
117
|
+
|
|
118
|
+
export function App() {
|
|
119
|
+
const [item] = useState<Item>({ id: 1, name: "apple" });
|
|
120
|
+
const [picked, setPicked] = useState<number[]>([]);
|
|
121
|
+
const [text, setText] = useState("");
|
|
122
|
+
return (
|
|
123
|
+
<>
|
|
124
|
+
<nv-info-card label="Fruit" count={picked.length} item={item} onitem-select={(e) => setPicked((p) => [...p, e.detail.id])} />
|
|
125
|
+
<nv-text-field value={text} onvalue-change={(e) => setText(e.detail.value)} />
|
|
126
|
+
<nv-light-card>
|
|
127
|
+
<div slot="title">{item && <span>{item.name}</span>}</div>
|
|
128
|
+
<div>{picked.map((id, i) => <p key={i}>{id}</p>)}</div>
|
|
129
|
+
</nv-light-card>
|
|
130
|
+
</>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
A template ref is typed from the same map: `useRef<HTMLElementTagNameMap["nv-info-card"]>(null)`.
|
|
136
|
+
|
|
137
|
+
## Known limitations
|
|
138
|
+
|
|
139
|
+
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: in Preact, `item={item}` becomes the attribute `item="[object Object]"`, the component gets `null` after `define()`, and the attribute stays even after a later new object is set as a property. A property set before `define()` is kept: Nativele takes it over when the element is upgraded. So when a module really is loaded later, set object props through the framework's property syntax, or, where it has none (Preact), from a ref: `ref={(el) => { if (el) el.item = item; }}`, and leave `item={item}` out of that tag, or Preact also writes the attribute.
|
|
140
|
+
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.
|
|
141
|
+
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. In Preact, toggling or reordering such children throws `NotFoundError: Failed to execute 'moveBefore' on 'Element'` (or `'insertBefore' on 'Node'`) and the slot loses its children. Use one of:
|
|
142
|
+
- `shadow: true` on the component, so the browser assigns slots natively and the children can change freely;
|
|
143
|
+
- 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`.
|
|
144
|
+
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`.
|
|
145
|
+
5. **Lifecycle.** `connected()` runs each time the element is inserted and `disconnected()` each time it is removed, and `bind` subscriptions end on removal and start again on insertion. A framework that moves an element (reordering a keyed list) with `insertBefore` runs both; one that moves it with `moveBefore` keeps it connected, so neither runs and its subscriptions continue. Preact 11 uses `moveBefore` where the browser has it (Chrome does) and `insertBefore` elsewhere. Either way the moved element keeps its props and the children in its Light DOM slots. So subscribe in `connected()`, and do not rely on a move running it.
|
|
146
|
+
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: with the Preact declaration, `tsc` reports a wrong prop type, a wrong object shape, a wrong use of `e.detail`, an unknown prop name, an event prop in the wrong case (`onItemSelect`) and an unknown tag, but not a hyphenated attribute name (`selected-item`), which TypeScript never checks in JSX; `vue-tsc` reports a wrong prop type or event detail, but not an unknown prop name.
|
|
147
|
+
7. Not verified with Preact: server-side rendering and hydration, `preact/compat`, signals passed as props, and Preact 10.
|
|
148
|
+
|
|
149
|
+
## Verify
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
npx ts-dom check src/nv # expect: ts-dom check: no issues
|
|
153
|
+
npx ts-dom doctor src/nv # expect: ts-dom doctor: the set-up looks right (info lines are suggestions)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
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, so the declaration is proved to type props. In TSX, `{/* @ts-expect-error */}` on the line before the tag works:
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
{/* @ts-expect-error count is a number */}
|
|
160
|
+
<nv-info-card count="1" />
|
|
161
|
+
{/* @ts-expect-error detail is { id: number } */}
|
|
162
|
+
<nv-info-card onitem-select={(e) => e.detail.name} />
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The type check passes only while each of these is an error; if the declaration stops working, it reports `TS2578: Unused '@ts-expect-error' directive`. A template comment such as `<!-- @ts-expect-error -->` is often not honored; check that a wrong prop fails without it.
|
|
166
|
+
|
|
167
|
+
In the browser, render a component with an object prop and an event listener, and check:
|
|
168
|
+
|
|
169
|
+
- the element has no attribute for the object prop (`el.getAttributeNames()`) and `el.<prop>` is the object;
|
|
170
|
+
- the listener runs once per event;
|
|
171
|
+
- 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.
|
|
172
|
+
|
|
173
|
+
The example project's [`verify.mjs`](https://github.com/Pixmi/nativele/blob/main/skills/nativele-integration/example/verify.mjs) does these checks for Preact with Playwright, plus a component defined after render, keyed reordering of hosts with and without `moveBefore`, and the end of `bind` on removal.
|
|
174
|
+
|
|
175
|
+
## Troubleshooting
|
|
176
|
+
|
|
177
|
+
| Symptom | Cause | Fix |
|
|
178
|
+
|---|---|---|
|
|
179
|
+
| `TS2307: Cannot find module './x.html.js'` from the build on a clean checkout | the type check runs before the generated files exist | start the build script with `ts-dom gen src/nv &&` (step 6) |
|
|
180
|
+
| 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, or a ref (limitation 1) |
|
|
181
|
+
| 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 |
|
|
182
|
+
| The event listener never runs | the name was changed (camelCase; Preact's `onItemSelect` listens to `itemselect`), or the listener is on an ancestor | listen with the exact name (`onitem-select`), on the element that emits |
|
|
183
|
+
| Children vanish, appear outside the slot, or the framework throws during an update (Preact: `NotFoundError: Failed to execute 'moveBefore'`) | framework-managed children inside a Light DOM component with slots | `shadow: true`, or a stable element per slot |
|
|
184
|
+
| `connected()` does not run when a keyed list reorders | the framework moved the element with `moveBefore`, so it stayed connected | nothing to fix: its subscriptions continue (limitation 5) |
|
|
185
|
+
| `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 |
|
|
186
|
+
| `Property 'nv-info-card' does not exist on type 'JSX.IntrinsicElements'`, or 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` |
|
|
187
|
+
|
|
188
|
+
## See also
|
|
189
|
+
|
|
190
|
+
- Props, events and data flow: https://github.com/Pixmi/nativele/blob/main/docs/components.md#data-flow-props-down-events-up
|
|
191
|
+
- Slots, Light DOM and Shadow DOM: https://github.com/Pixmi/nativele/blob/main/docs/components.md#light-default-and-shadow
|
|
192
|
+
- Lifecycle: https://github.com/Pixmi/nativele/blob/main/docs/components.md#components
|
|
193
|
+
- Moved elements, connecting and subscriptions: https://github.com/Pixmi/nativele/blob/main/docs/lists.md#moving-items-connecting-and-subscriptions
|
|
194
|
+
- Setup and checks: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md
|
|
195
|
+
- Complete framework integrations: `npx ts-dom skills nativele-react`, `npx ts-dom skills nativele-vue`
|
|
@@ -3,7 +3,7 @@ name: nativele-react
|
|
|
3
3
|
description: "Use Nativele web components inside a React 19 app: passing props, receiving events, children and slots, lifecycle, and TSX types. Use when a project depends on react and @nativele/ts-dom."
|
|
4
4
|
metadata:
|
|
5
5
|
tier: official
|
|
6
|
-
nativele: ">=0.5.
|
|
6
|
+
nativele: ">=0.5.1 <0.6.0"
|
|
7
7
|
verified: "2026-11-05"
|
|
8
8
|
tools: "react@19.3.1 react-dom@19.3.1 @types/react@19.3.0 @types/react-dom@19.3.0 vite@8.3.5 @vitejs/plugin-react@6.2.1 typescript@7.0.2"
|
|
9
9
|
author: "Nativele maintainers"
|
|
@@ -24,20 +24,21 @@ Nativele components are native custom elements. React renders them like any othe
|
|
|
24
24
|
|
|
25
25
|
| Tool | Verified version |
|
|
26
26
|
|---|---|
|
|
27
|
-
| @nativele/ts-dom | 0.5.
|
|
27
|
+
| @nativele/ts-dom | 0.5.1 |
|
|
28
28
|
| react, react-dom | 19.3.1 |
|
|
29
29
|
| @types/react, @types/react-dom | 19.3.0 |
|
|
30
30
|
| vite | 8.3.5 |
|
|
31
31
|
| @vitejs/plugin-react | 6.2.1 |
|
|
32
32
|
| typescript | 7.0.2 |
|
|
33
33
|
|
|
34
|
-
Verified on 2026-11-05 in Google Chrome 148 with the example project in the Nativele repository (`skills/nativele-react/example`), which CI runs again when the
|
|
34
|
+
Verified on 2026-11-05 in Google Chrome 148 with the example project in the Nativele repository (`skills/nativele-react/example`), which CI runs again when the example or the library changes and once a month. With a different major version of React, treat every step as unverified.
|
|
35
35
|
|
|
36
36
|
## Set-up
|
|
37
37
|
|
|
38
|
-
1. Keep the Nativele components in their own folder, for example `src/nv/`, each as `x.html` plus `x.ts
|
|
38
|
+
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`.
|
|
39
39
|
2. Install: `npm i @nativele/ts-dom`.
|
|
40
|
-
3.
|
|
40
|
+
3. Set `"strict": true` in the tsconfig whose `include` covers that folder (`tsconfig.app.json` in Vite's React template, which leaves it off); Nativele's type checks depend on it. Other configs, such as the one for `vite.config.ts`, stay as they are.
|
|
41
|
+
4. Add the Nativele Vite plugin next to the React plugin, pointing at that folder:
|
|
41
42
|
|
|
42
43
|
```ts
|
|
43
44
|
// vite.config.ts
|
|
@@ -48,7 +49,11 @@ Verified on 2026-11-05 in Google Chrome 148 with the example project in the Nati
|
|
|
48
49
|
export default defineConfig({ plugins: [tsDom({ dirs: ["src/nv"] }), react()] });
|
|
49
50
|
```
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
Pass the same folder to every `ts-dom` command (`npx ts-dom check src/nv`), so there is one `ts-dom-tags.ts`, in `src/nv`.
|
|
53
|
+
|
|
54
|
+
5. If the project lints with oxlint (`.oxlintrc.json`, as in Vite's React template), load the ts-dom rules there: add `{ "name": "ts-dom", "specifier": "@nativele/ts-dom/oxlint" }` to `jsPlugins` and `"ts-dom/unused-markup": "error"` to `rules`.
|
|
55
|
+
|
|
56
|
+
6. Import every Nativele component module in the entry file, before React renders anything:
|
|
52
57
|
|
|
53
58
|
```tsx
|
|
54
59
|
// src/main.tsx
|
|
@@ -59,7 +64,7 @@ Verified on 2026-11-05 in Google Chrome 148 with the example project in the Nati
|
|
|
59
64
|
|
|
60
65
|
React 19 sets a value as a property only when the element is already defined; before that it writes an attribute, and an object prop becomes the text `"[object Object]"`.
|
|
61
66
|
|
|
62
|
-
|
|
67
|
+
7. Add the TSX declaration below as `src/nativele-react.d.ts`. It reads the tag map that `ts-dom gen` writes (`ts-dom-tags.ts`), so every exported component gets typed props and events, and new components need no edit:
|
|
63
68
|
|
|
64
69
|
```ts
|
|
65
70
|
// src/nativele-react.d.ts
|
|
@@ -84,6 +89,8 @@ Verified on 2026-11-05 in Google Chrome 148 with the example project in the Nati
|
|
|
84
89
|
|
|
85
90
|
The component class must be exported and passed to `define()` in a folder that `ts-dom gen` scans, so it appears in `ts-dom-tags.ts`.
|
|
86
91
|
|
|
92
|
+
The generated files are not committed, so on a clean checkout the type check in `npm run build` runs before anything has written them and fails with `TS2307: Cannot find module './x.html.js'`. Generate them first: `"build": "ts-dom gen src/nv && tsc -b && vite build"` (the template's script with `ts-dom gen src/nv &&` in front).
|
|
93
|
+
|
|
87
94
|
## Example
|
|
88
95
|
|
|
89
96
|
A Nativele component with props and an event:
|
|
@@ -132,6 +139,12 @@ Used from React:
|
|
|
132
139
|
|
|
133
140
|
A component with slots that receives React children: give it `shadow: true`, then pass children directly.
|
|
134
141
|
|
|
142
|
+
```html
|
|
143
|
+
<!-- src/nv/shadow-card.html -->
|
|
144
|
+
<h2><slot name="title">Untitled</slot></h2>
|
|
145
|
+
<div><slot></slot></div>
|
|
146
|
+
```
|
|
147
|
+
|
|
135
148
|
```ts
|
|
136
149
|
// src/nv/shadow-card.ts
|
|
137
150
|
export class ShadowCard extends Component(tpl, { shadow: true }) {}
|
|
@@ -147,9 +160,9 @@ define("shadow-card", ShadowCard);
|
|
|
147
160
|
|
|
148
161
|
## Known limitations
|
|
149
162
|
|
|
150
|
-
1. **Define before render.** Import every Nativele module before React first renders its tag (step
|
|
163
|
+
1. **Define before render.** Import every Nativele module before React first renders its tag (step 6). Otherwise props become attributes and object props are lost.
|
|
151
164
|
2. **Event prop names.** Only `on` plus the exact event name works (`onitem-select`); `onItemSelect` and `onItem-select` never fire. Nativele events do not bubble by default, so put the handler on the element that emits. A ref with `addEventListener` in `useEffect` also works (remove it in the cleanup).
|
|
152
|
-
3. **Light DOM slots and React children do not mix.** In the default Light DOM mode, Nativele moves the host's children into its slot positions. When React later inserts, removes or reorders those children it fails (`insertBefore`/`removeChild` errors that unmount the root) or leaves nodes outside the slot. Changing text in place and appending at the end happen to work, but do not rely on that. Use one of:
|
|
165
|
+
3. **Light DOM slots and React children do not mix.** In the default Light DOM mode (`Component(tpl)` with no `shadow`), Nativele moves the host's children into its slot positions. When React later inserts, removes or reorders those children it fails (`insertBefore`/`removeChild` errors that unmount the root) or leaves nodes outside the slot. Changing text in place and appending at the end happen to work, but do not rely on that. Use one of:
|
|
153
166
|
- `shadow: true` on the component (children can then change freely), or
|
|
154
167
|
- one stable React element per slot, and let React change only what is inside it:
|
|
155
168
|
|
|
@@ -160,9 +173,9 @@ define("shadow-card", ShadowCard);
|
|
|
160
173
|
</light-card>
|
|
161
174
|
```
|
|
162
175
|
|
|
163
|
-
A stable but empty wrapper still fills its slot, so the slot's fallback content does not show
|
|
176
|
+
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`.
|
|
164
177
|
4. **New objects for changed props.** A Nativele component compares props by reference; pass a new object when the data changes (React state updates usually do).
|
|
165
|
-
5. **TSX types need the declaration** in step
|
|
178
|
+
5. **TSX types need the declaration** in step 7; without it, every Nativele tag is `TS2339: Property 'info-card' does not exist on type 'JSX.IntrinsicElements'`. Check it with a `.tsx` that has a wrong prop under `// @ts-expect-error`, because `skipLibCheck` can hide mistakes inside the declaration itself. Keep these cases in a `.tsx` file of their own that the app never imports (the example uses `src/types-check.tsx`).
|
|
166
179
|
6. Not verified: server-side rendering, form controls inside Nativele components submitting with a React form, and React versions other than 19.3.
|
|
167
180
|
|
|
168
181
|
React StrictMode is fine: it runs effects twice but does not insert the custom element twice, so `connected()` runs once, and `bind` subscriptions are released when React unmounts the element.
|
|
@@ -174,16 +187,19 @@ Run in the project:
|
|
|
174
187
|
```sh
|
|
175
188
|
npx ts-dom check src/nv # expect: ts-dom check: no issues
|
|
176
189
|
npx ts-dom doctor src/nv # expect: ts-dom doctor: the set-up looks right
|
|
177
|
-
npx tsc --noEmit
|
|
178
|
-
|
|
190
|
+
npx tsc --noEmit -p tsconfig.app.json # expect: no errors (keep one @ts-expect-error case per component kind)
|
|
191
|
+
npm run build # expect: ✓ built (it runs ts-dom gen, tsc -b and vite build)
|
|
179
192
|
```
|
|
180
193
|
|
|
181
|
-
|
|
194
|
+
Pass `tsc` the tsconfig from step 3. A root `tsconfig.json` that only lists `references` (as in Vite's template) checks nothing itself, so `tsc --noEmit` without `-p` passes even with type errors.
|
|
195
|
+
|
|
196
|
+
In the browser, render a component with an object prop and an event handler, and check that the element has no `item` attribute (`el.getAttributeNames()`), that `el.item` is the object, and that the handler fires once per click. The example project's [`verify.mjs`](https://github.com/Pixmi/nativele/blob/main/skills/nativele-react/example/verify.mjs) does these checks with Playwright, plus children that React reorders, adds and removes inside a `shadow: true` component and inside stable wrappers in a Light DOM component. A Light DOM component has no `<slot>` elements once rendered; check where the children ended up with `el.assignedNodes(name)`, where `""` is the default slot.
|
|
182
197
|
|
|
183
198
|
## Troubleshooting
|
|
184
199
|
|
|
185
200
|
| Symptom | Cause | Fix |
|
|
186
201
|
|---|---|---|
|
|
202
|
+
| `TS2307: Cannot find module './x.html.js'` from `npm run build` on a clean checkout | `tsc -b` runs before the generated files exist | start the script with `ts-dom gen src/nv &&` |
|
|
187
203
|
| An object prop shows `[object Object]` or is missing | the component was rendered before `define()` ran | import the component module in the entry file before rendering |
|
|
188
204
|
| The event handler never runs | the prop name is camelCased (`onItemSelect`) | use `on` plus the exact event name (`onitem-select`) |
|
|
189
205
|
| `insertBefore`/`removeChild` errors, the React root disappears | React children changed inside a Light DOM component with slots | use `shadow: true`, or stable wrapper elements per slot |
|