@nativele/ts-dom 0.5.1 → 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 +33 -0
- package/README.md +6 -1
- package/README.zh-TW.md +6 -1
- package/dist/cli.mjs +1 -1
- package/dist/index.mjs +3 -3
- package/dist/{lint-DfUAh7t3.mjs → lint-Bwytj7L8.mjs} +1 -1
- package/dist/oxlint-plugin.mjs +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/skills/README.md +7 -4
- package/skills/nativele-integration/SKILL.md +92 -22
- package/skills/nativele-state-management/SKILL.md +319 -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-zod/SKILL.md +345 -0
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,39 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version
|
|
|
10
10
|
- Adding a lint rule is a minor change; lint rules are warnings or opt-in and do not count as breaking.
|
|
11
11
|
- Before every release run `npm run verify:pack` (`publint` and `attw`), `tsc`, the unit tests, the browser tests, `npm run smoke` and `npm run smoke:no-bundler`; all of them run in `.github/workflows/ci.yml`.
|
|
12
12
|
|
|
13
|
+
## 0.5.2
|
|
14
|
+
|
|
15
|
+
Skills for the libraries a Nativele app most often sits next to: state stores, testing, data fetching, form validation and Tailwind CSS. Each was accepted through agent trials, and the trials found two bugs in the library, fixed here.
|
|
16
|
+
|
|
17
|
+
### Skills
|
|
18
|
+
|
|
19
|
+
- New `nativele-state-management` (Official): where state lives (component `state`, `createStore` or an external store), and a selector adapter that connects Zustand, Redux Toolkit or nanostores to `bind`, so a component re-renders only when its slice changes. Its example project runs in CI.
|
|
20
|
+
- New `nativele-testing` (Official): component tests with Vitest and happy-dom or jsdom, real-browser tests with Vitest browser mode, and end-to-end tests with Playwright. It covers what the unit-test DOM cannot do (`moveBefore`, focus during a reorder), forms, slots, isolation between test files, and a clean checkout. Its example project runs in CI.
|
|
21
|
+
- New `nativele-tanstack-query` (Experimental): `@tanstack/query-core` observers through `bind`. It covers loading, error and refreshing states; keys from props; shared requests; mutations; and stopping requests when a component is removed.
|
|
22
|
+
- New `nativele-zod` (Experimental): form validation with Zod or Valibot. It covers per-field messages, nested and array fields, `aria-invalid` and descriptions (also with several instances on a page), cross-field checks, and the emitted data typed from the schema.
|
|
23
|
+
- New `nativele-tailwind` (Experimental): Tailwind CSS v4 in templates. It covers source detection (and excluding the installed Skills), `shadow: true` with an adopted stylesheet, `<style scoped>` defaults in a layer so utilities win, class-based dark mode in a shadow root, and templates outside the project.
|
|
24
|
+
- `nativele-integration` is now Official, verified with Preact 11 and an example project in CI. Its worked example is Preact:
|
|
25
|
+
- the JSX declaration that types Nativele tags;
|
|
26
|
+
- `onitem-select` event props;
|
|
27
|
+
- a ref that sets the property for a component defined after render;
|
|
28
|
+
- keyed moves, which keep the element connected where the browser has `moveBefore`.
|
|
29
|
+
|
|
30
|
+
### Fixes
|
|
31
|
+
|
|
32
|
+
- In a list item, a `data-ref` on an element inside a `data-if` block was missing from the item's `refs` (the generated type declared it, so the code compiled and then threw). The refs are now collected before the item hides its `data-if` blocks.
|
|
33
|
+
- `unused-ref` (and the matching checks for holes and lists) reported every ref as unused when the class held `this.refs` in a variable (`const r = this.refs; r.name.focus()`). Such an alias now counts as using every name, like `this.refs[k]`; destructuring (`const { name } = this.refs`) is still checked name by name.
|
|
34
|
+
|
|
35
|
+
### Documentation
|
|
36
|
+
|
|
37
|
+
- `bind`: it calls back at once and on every change, and only a `bind` in `connected()` comes back after re-insertion. A source's `subscribe` must return its unsubscribe function and must not call the listener when subscribing.
|
|
38
|
+
- Styles:
|
|
39
|
+
- a template's `<style>` is not in a cascade layer, so it beats layered framework styles unless its rules are put in a layer;
|
|
40
|
+
- a `shadow: true` component uses an outside stylesheet through `adoptedStyleSheets`.
|
|
41
|
+
- Lists: without `moveBefore` (older browsers, happy-dom, jsdom), a moved item reconnects and loses focus.
|
|
42
|
+
- Slots: children added later are placed after a microtask.
|
|
43
|
+
- `define()`: defining a name a second time keeps the first class.
|
|
44
|
+
- Form recipe: `aria-invalid` must be `"true"`, and blur validation needs `data-on-focusout`.
|
|
45
|
+
|
|
13
46
|
## 0.5.1
|
|
14
47
|
|
|
15
48
|
Nativele components now have a Skill for each major framework, every Skill is accepted only after a fresh agent has done real work with it, and the checks that those trials found wrong in real framework projects are fixed.
|
package/README.md
CHANGED
|
@@ -63,10 +63,15 @@ npx ts-dom skills add nativele-core # copy it into .claude/skills/ (when the
|
|
|
63
63
|
| `nativele-core` | building components: the `ts-dom check` loop, templates, state, lists, data flow |
|
|
64
64
|
| `nativele-react` | Nativele components in React 19: props, events, children and slots, lifecycle, TSX types |
|
|
65
65
|
| `nativele-vue` | Nativele components in Vue 3: props, events, `v-model`, slots, `<KeepAlive>`, template types |
|
|
66
|
-
| `nativele-svelte` | Nativele components in Svelte 5: props, events, slots, template types
|
|
66
|
+
| `nativele-svelte` | Nativele components in Svelte 5: props, events, slots, template types |
|
|
67
67
|
| `nativele-angular` | Nativele components in Angular: bindings, change detection, the build, content projection (Experimental) |
|
|
68
68
|
| `nativele-vite` | Nativele in a Vite app: the plugin and `root`, multi-page builds, hot updates |
|
|
69
69
|
| `nativele-integration` | Nativele components in a framework without its own Skill (Preact, Solid, ...): properties, events, slots, lifecycle, template types (Experimental) |
|
|
70
|
+
| `nativele-state-management` | where state lives; `createStore`, and Zustand, Redux Toolkit or nanostores through `bind` |
|
|
71
|
+
| `nativele-testing` | component tests with Vitest, real-browser tests |
|
|
72
|
+
| `nativele-tanstack-query` | data fetching with `@tanstack/query-core` (Experimental) |
|
|
73
|
+
| `nativele-zod` | form validation with Zod or Valibot (Experimental) |
|
|
74
|
+
| `nativele-tailwind` | Tailwind CSS in templates and Shadow DOM (Experimental) |
|
|
70
75
|
|
|
71
76
|
Tiers: Official Skills are verified by the maintainers against an example project in CI; Community Skills are contributed and show their author and last verified versions; Experimental Skills are not fully verified yet. `ts-dom skills` marks a Skill as stale when its verification is more than six months old or does not cover the installed version.
|
|
72
77
|
|
package/README.zh-TW.md
CHANGED
|
@@ -63,10 +63,15 @@ npx ts-dom skills add nativele-core # 複製到 .claude/skills/(專案有 .
|
|
|
63
63
|
| `nativele-core` | 開發元件:`ts-dom check` 循環、範本、state、清單、資料流 |
|
|
64
64
|
| `nativele-react` | 在 React 19 中使用 Nativele 元件:props、事件、子元素與 slot、生命週期、TSX 型別 |
|
|
65
65
|
| `nativele-vue` | 在 Vue 3 中使用 Nativele 元件:props、事件、`v-model`、slot、`<KeepAlive>`、範本型別 |
|
|
66
|
-
| `nativele-svelte` | 在 Svelte 5 中使用 Nativele 元件:props、事件、slot
|
|
66
|
+
| `nativele-svelte` | 在 Svelte 5 中使用 Nativele 元件:props、事件、slot、範本型別 |
|
|
67
67
|
| `nativele-angular` | 在 Angular 中使用 Nativele 元件:綁定、變更偵測、建置流程、內容投影(Experimental) |
|
|
68
68
|
| `nativele-vite` | 在 Vite 應用中使用 Nativele:外掛與 `root`、多頁建置、熱更新 |
|
|
69
69
|
| `nativele-integration` | 在沒有專屬 Skill 的框架(Preact、Solid 等)中使用 Nativele 元件:property、事件、slot、生命週期、範本型別(Experimental) |
|
|
70
|
+
| `nativele-state-management` | 狀態放在哪裡;`createStore`,以及透過 `bind` 接上 Zustand、Redux Toolkit、nanostores |
|
|
71
|
+
| `nativele-testing` | 用 Vitest 做元件測試、真實瀏覽器測試 |
|
|
72
|
+
| `nativele-tanstack-query` | 用 `@tanstack/query-core` 取資料(Experimental) |
|
|
73
|
+
| `nativele-zod` | 用 Zod 或 Valibot 驗證表單(Experimental) |
|
|
74
|
+
| `nativele-tailwind` | 在範本與 Shadow DOM 中使用 Tailwind CSS(Experimental) |
|
|
70
75
|
|
|
71
76
|
等級:Official 由維護者以 CI 中的範例專案驗證;Community 由社群貢獻,標示作者與最後驗證版本;Experimental 尚未充分驗證。驗證超過六個月,或不涵蓋已安裝的版本時,`ts-dom skills` 會標示為可能過期。
|
|
72
77
|
|
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { _ as watchAll, a as analyze, c as generateAll, d as isSelected, f as listHtml, g as staleGenerated, i as CodegenError, m as renderTags, n as checkA11y, o as checkGenerated, p as listTs, r as CODEGEN_RULES, t as A11Y_RULES } from "./a11y-CV3nBgBn.mjs";
|
|
3
|
-
import { c as lintUsage, n as UNUSED_MARKUP_RULES, o as formatIssue, s as lintDataFlow, t as DATA_FLOW_RULES } from "./lint-
|
|
3
|
+
import { c as lintUsage, n as UNUSED_MARKUP_RULES, o as formatIssue, s as lintDataFlow, t as DATA_FLOW_RULES } from "./lint-Bwytj7L8.mjs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { cpSync, existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
6
6
|
import { dirname, join, relative, resolve } from "node:path";
|
package/dist/index.mjs
CHANGED
|
@@ -268,14 +268,14 @@ var List = class {
|
|
|
268
268
|
holder.append(el);
|
|
269
269
|
const conds = mountConds(holder);
|
|
270
270
|
const text = mountHoles(holder);
|
|
271
|
-
conds.hideAll();
|
|
272
|
-
const show = conds.show;
|
|
273
|
-
el.remove();
|
|
274
271
|
const refs = {};
|
|
275
272
|
for (const n of [el, ...el.querySelectorAll("[data-ref]")]) {
|
|
276
273
|
const name = n.getAttribute("data-ref");
|
|
277
274
|
if (name) refs[name] = n;
|
|
278
275
|
}
|
|
276
|
+
conds.hideAll();
|
|
277
|
+
const show = conds.show;
|
|
278
|
+
el.remove();
|
|
279
279
|
this.onCreate?.(el);
|
|
280
280
|
const entry = {
|
|
281
281
|
el,
|
|
@@ -87,7 +87,7 @@ function unusedMarkup(html, code) {
|
|
|
87
87
|
message,
|
|
88
88
|
...at(a, key)
|
|
89
89
|
});
|
|
90
|
-
const dynamic = (obj) => used(`\\b${obj}\\[\\s*[^"'\\s\\]]`);
|
|
90
|
+
const dynamic = (obj) => used(`\\b${obj}\\[\\s*[^"'\\s\\]]`) || used(`[\\w$]\\s*=\\s*this\\.${obj}\\s*[;,)\\n]|[(,]\\s*this\\.${obj}\\s*[,)]`);
|
|
91
91
|
const dynRefs = dynamic("refs");
|
|
92
92
|
const dynText = dynamic("text");
|
|
93
93
|
const dynLists = dynamic("lists");
|
package/dist/oxlint-plugin.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as checkA11y, t as A11Y_RULES } from "./a11y-CV3nBgBn.mjs";
|
|
2
|
-
import { a as dataFlow, i as contractsFor, l as templateCode, n as UNUSED_MARKUP_RULES, r as componentClasses, t as DATA_FLOW_RULES, u as unusedMarkup } from "./lint-
|
|
2
|
+
import { a as dataFlow, i as contractsFor, l as templateCode, n as UNUSED_MARKUP_RULES, r as componentClasses, t as DATA_FLOW_RULES, u as unusedMarkup } from "./lint-Bwytj7L8.mjs";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { dirname, resolve } from "node:path";
|
|
5
5
|
// src/lib/oxlint-plugin.ts
|
package/llms.txt
CHANGED
|
@@ -79,7 +79,7 @@ define("my-counter", MyCounter);
|
|
|
79
79
|
- `data-each` is never nested: a list inside an item is the item component's own `data-each`.
|
|
80
80
|
|
|
81
81
|
## Rules for agents
|
|
82
|
-
- Skills: `npx ts-dom skills` lists the task procedures shipped with the package (`nativele-core` for building components, `nativele-react`, `nativele-vue`, `nativele-svelte` and `nativele-angular` for those frameworks, `nativele-vite` for Vite, `nativele-integration` for other frameworks, ...), each with verified versions; `npx ts-dom skills <name>` prints one, `npx ts-dom skills add <name>` copies it into `.claude/skills/` or `.agents/skills/`, and `npx ts-dom doctor` suggests the ones that match the project's dependencies.
|
|
82
|
+
- Skills: `npx ts-dom skills` lists the task procedures shipped with the package (`nativele-core` for building components, `nativele-react`, `nativele-vue`, `nativele-svelte` and `nativele-angular` for those frameworks, `nativele-vite` for Vite, `nativele-integration` for other frameworks, `nativele-state-management`, `nativele-testing`, `nativele-tanstack-query`, `nativele-zod` and `nativele-tailwind` for those tools, ...), each with verified versions; `npx ts-dom skills <name>` prints one, `npx ts-dom skills add <name>` copies it into `.claude/skills/` or `.agents/skills/`, and `npx ts-dom doctor` suggests the ones that match the project's dependencies.
|
|
83
83
|
- `npx ts-dom rules --explain=<id>` (or `rules --json`) explains any rule id from a diagnostic: why it exists, a wrong example and the fix.
|
|
84
84
|
- Markup well-formedness is a formatter's job: `oxfmt --check` reports misnested tags that the library's parser would silently repair. Generate with `--whitespace=collapse` when a formatter is used.
|
|
85
85
|
- If checks behave oddly (no types, a template not found), run `npx ts-dom doctor --json` first: it reports tsconfig, import path, runtime, generated file and plugin problems with a `Fix:` each.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nativele/ts-dom",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Designed for AI agents. Driven by TypeScript. Defined in vanilla HTML. A lightweight library for building web components: typed templates, native custom elements, no virtual DOM, no template expressions, no bundler required.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"html",
|
package/skills/README.md
CHANGED
|
@@ -125,11 +125,14 @@ Otherwise the maintainers send the gaps back to the contributor as review commen
|
|
|
125
125
|
|---|---|---|---|
|
|
126
126
|
| `nativele-core` | Official | available | building with Nativele itself: the `ts-dom check` loop, components, lists, data flow |
|
|
127
127
|
| `nativele-react` | Official | available | Nativele components in React 19: props, events, children and slots, lifecycle, TSX types |
|
|
128
|
-
| `nativele-integration` |
|
|
128
|
+
| `nativele-integration` | Official | available | Nativele components in a framework without its own Skill: defining before render, properties and attributes, events, slots, lifecycle, types; verified with Preact |
|
|
129
129
|
| `nativele-vite` | Official | available | Nativele in a Vite app: the plugin and `root`, multi-page builds, the build script, hot updates, Vite+ |
|
|
130
130
|
| `nativele-vue` | Official | available | Nativele components in Vue 3: `isCustomElement`, props, events, `v-model`, slots, `<KeepAlive>`, template types |
|
|
131
131
|
| `nativele-svelte` | Experimental | available | Nativele components in Svelte 5: props, event attributes, slots, `bind:this`, template types |
|
|
132
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 |
|
|
134
|
-
| `nativele-testing` | Official |
|
|
135
|
-
|
|
|
133
|
+
| `nativele-state-management` | Official | available | where state lives; `createStore`, and Zustand, Redux Toolkit or nanostores through `bind` with a selector adapter |
|
|
134
|
+
| `nativele-testing` | Official | available | component tests with Vitest and happy-dom or jsdom, real-browser tests with Vitest browser mode or Playwright |
|
|
135
|
+
| `nativele-tanstack-query` | Experimental | available | `@tanstack/query-core` observers through `bind`: loading and error states, keys from props, mutations |
|
|
136
|
+
| `nativele-zod` | Experimental | available | form validation with Zod or Valibot: per-field messages, nested and array fields, `aria-invalid` |
|
|
137
|
+
| `nativele-tailwind` | Experimental | available | Tailwind CSS v4 in templates: source detection, `shadow: true` with adopted style sheets, `<style scoped>` layers |
|
|
138
|
+
| Other libraries | Experimental or Community | open for contributions | |
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
name: nativele-integration
|
|
3
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
4
|
metadata:
|
|
5
|
-
tier:
|
|
5
|
+
tier: official
|
|
6
6
|
nativele: ">=0.5.1 <0.6.0"
|
|
7
7
|
verified: "2026-11-05"
|
|
8
|
-
tools: "
|
|
8
|
+
tools: "preact@11.1.1 vite@8.3.6 @preact/preset-vite@2.10.6 typescript@7.0.2"
|
|
9
9
|
author: "Nativele maintainers"
|
|
10
10
|
detects: "preact solid-js"
|
|
11
11
|
---
|
|
@@ -16,7 +16,7 @@ metadata:
|
|
|
16
16
|
|
|
17
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
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.
|
|
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
20
|
- Not for writing the Nativele components themselves: use `nativele-core` (`npx ts-dom skills add nativele-core`).
|
|
21
21
|
|
|
22
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.
|
|
@@ -26,18 +26,17 @@ Nativele components are native custom elements. The framework renders their tags
|
|
|
26
26
|
| Tool | Verified version |
|
|
27
27
|
|---|---|
|
|
28
28
|
| @nativele/ts-dom | 0.5.1 |
|
|
29
|
-
|
|
|
29
|
+
| preact | 11.1.1 |
|
|
30
30
|
| vite | 8.3.6 |
|
|
31
|
-
| @
|
|
32
|
-
|
|
|
33
|
-
| typescript | 6.0.3 |
|
|
31
|
+
| @preact/preset-vite | 2.10.6 |
|
|
32
|
+
| typescript | 7.0.2 |
|
|
34
33
|
|
|
35
|
-
Verified on 2026-11-05
|
|
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.
|
|
36
35
|
|
|
37
36
|
## Set-up
|
|
38
37
|
|
|
39
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.
|
|
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
|
|
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.
|
|
41
40
|
3. Find out, from the framework's documentation on custom elements (often called "web components"), these four things before writing any template:
|
|
42
41
|
|
|
43
42
|
| Question | Why it matters |
|
|
@@ -50,14 +49,15 @@ Verified on 2026-11-05 by an agent that followed this Skill in a fresh Vue app m
|
|
|
50
49
|
4. Import every Nativele component module in the app's entry file, before the framework mounts anything:
|
|
51
50
|
|
|
52
51
|
```ts
|
|
53
|
-
// src/main.
|
|
52
|
+
// src/main.tsx
|
|
54
53
|
import "./nv/info-card.js";
|
|
55
54
|
// then create and mount the app
|
|
56
55
|
```
|
|
57
56
|
|
|
58
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.
|
|
59
58
|
|
|
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`
|
|
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
61
|
|
|
62
62
|
## Example
|
|
63
63
|
|
|
@@ -77,18 +77,74 @@ card.item = { id: 2, name: "Grace" }; // a new object re-renders; ch
|
|
|
77
77
|
card.remove(); // disconnected() runs, bind() subscriptions end
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
In a framework, the same calls come from its template syntax. `nativele-vue` (`npx ts-dom skills nativele-vue`) is a complete
|
|
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)`.
|
|
81
136
|
|
|
82
137
|
## Known limitations
|
|
83
138
|
|
|
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
|
|
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.
|
|
85
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.
|
|
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:
|
|
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:
|
|
87
142
|
- `shadow: true` on the component, so the browser assigns slots natively and the children can change freely;
|
|
88
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`.
|
|
89
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`.
|
|
90
|
-
5. **Lifecycle.** `connected()` runs each time the element is inserted and `disconnected()` each time it is removed
|
|
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.
|
|
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.
|
|
92
148
|
|
|
93
149
|
## Verify
|
|
94
150
|
|
|
@@ -97,7 +153,16 @@ npx ts-dom check src/nv # expect: ts-dom check: no issues
|
|
|
97
153
|
npx ts-dom doctor src/nv # expect: ts-dom doctor: the set-up looks right (info lines are suggestions)
|
|
98
154
|
```
|
|
99
155
|
|
|
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
|
|
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.
|
|
101
166
|
|
|
102
167
|
In the browser, render a component with an object prop and an event listener, and check:
|
|
103
168
|
|
|
@@ -105,21 +170,26 @@ In the browser, render a component with an object prop and an event listener, an
|
|
|
105
170
|
- the listener runs once per event;
|
|
106
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.
|
|
107
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
|
+
|
|
108
175
|
## Troubleshooting
|
|
109
176
|
|
|
110
177
|
| Symptom | Cause | Fix |
|
|
111
178
|
|---|---|---|
|
|
112
|
-
|
|
|
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) |
|
|
113
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 |
|
|
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 |
|
|
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) |
|
|
116
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 |
|
|
117
|
-
|
|
|
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` |
|
|
118
187
|
|
|
119
188
|
## See also
|
|
120
189
|
|
|
121
190
|
- Props, events and data flow: https://github.com/Pixmi/nativele/blob/main/docs/components.md#data-flow-props-down-events-up
|
|
122
191
|
- Slots, Light DOM and Shadow DOM: https://github.com/Pixmi/nativele/blob/main/docs/components.md#light-default-and-shadow
|
|
123
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
|
|
124
194
|
- Setup and checks: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md
|
|
125
|
-
-
|
|
195
|
+
- Complete framework integrations: `npx ts-dom skills nativele-react`, `npx ts-dom skills nativele-vue`
|