@nativele/ts-dom 0.0.0-stage → 0.2.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/llms.txt ADDED
@@ -0,0 +1,85 @@
1
+ # Nativele
2
+
3
+ > A TypeScript-driven, HTML-first UI library. Bind HTML templates to TypeScript custom elements. HTML describes structure and where data goes; TypeScript owns state and behavior; the DOM is the only view. Fully typed by tsc, no virtual DOM, no bundler required.
4
+
5
+ Workflow: write `x.html` + `x.ts`, run `npx ts-dom gen` (writes `x.html.ts`), then `npx tsc`. Import a template as `import tpl from "./x.html.js"` (note the `.js`). Lint with `npx ts-dom lint` (add `--a11y`). `npx ts-dom rules` lists every rule id. Every diagnostic is `file:line:col severity [rule-id] message` and ends with a `Fix:` hint: read the hint and apply it.
6
+
7
+ ## Template syntax (HTML only names things; no expressions)
8
+ - `{{ name }}`: text hole, a single identifier only. NOT `{{ a.b }}` / `{{ a + 1 }}`. Not allowed in attributes.
9
+ - `data-ref="x"`: element available as `this.refs.x` (typed by tag).
10
+ - `data-if="x"`: hidden by default (the element is REMOVED from the DOM, replaced by a comment); shown when state/props/derive `x` is truthy (or `this.show("x", true)`).
11
+ - `<template data-each="items"><li>...</li></template>`: list with exactly one root element; fill it with `this.lists.items.replace(array, item => item.id, (item, { text, refs, show, el }) => {...})`. Not nestable; no `<slot>`, no `data-if` on the root.
12
+ - `data-on-<event>="method"`: calls the public method `method(e: Event, hit: EventHit)` on the component. Method name only (no calls). Event name lowercase. Inside a list item `hit.key` is the item key, also for events emitted by a child component placed in the item. Do not name a method like a DOM/ts-dom member (`remove`, `click`, `focus`, `set`, `render`, ...).
13
+ - `<slot name="x">`: Light DOM by default (library moves children); native with `shadow: true`.
14
+ - `<style>` must be top-level in the HTML file; `<style scoped>` limits it to the component (Light DOM).
15
+ - Using another component: just write its tag (`<form-field data-ref="f" label="Name" data-on-field-input="onName">`); the generated module imports it automatically.
16
+
17
+ ## Component (TypeScript)
18
+ ```ts
19
+ import { Component, define, event } from "@nativele/ts-dom";
20
+ import type { EmitEvent, EventHit } from "@nativele/ts-dom";
21
+ import tpl from "./counter.html.js";
22
+
23
+ class MyCounter extends Component(tpl, {
24
+ state: { count: 0 }, // owned by the component, change ONLY with this.set()
25
+ props: { step: 1 }, // owned by the parent, read-only here (string/number/boolean default => settable from an HTML attribute `step="2"`)
26
+ derive: { double: (s) => s.count * 2 }, // recomputed after every change; usable by {{ double }} / data-if
27
+ emits: { "count-change": event<{ count: number }>() }, // events this component sends up (do not bubble by default)
28
+ // shadow: true, // optional Shadow DOM
29
+ }) {
30
+ inc() { // referenced by data-on-click="inc"
31
+ this.set((s) => ({ count: s.count + this.props.step }));
32
+ this.emit("count-change", { count: this.state.count });
33
+ }
34
+ override render(view, changed) { /* only for what automatic binding cannot do: lists, attributes, focus */ }
35
+ }
36
+ define("my-counter", MyCounter);
37
+ ```
38
+ Rules the compiler enforces: every `{{ hole }}` / `data-if` name must exist in state, props or derive (error `missingStateKeys`); state/props/derive names must be unique (`conflictingKeys`); a prop must not be named like an HTMLElement/ts-dom member such as `title`, `hidden`, `render` (`reservedPropNames`); emitted event names must not be native events (`nativeEventNames`); every `data-on-*` method must exist as a public method (`missingHandlers`, reported by `define()`).
39
+
40
+ ## Data flow (one direction)
41
+ - State: changed only by its owner with `this.set(patch | s => patch)`; no change => no render. `this.batch(fn)` merges several updates. Rendering is synchronous.
42
+ - Props go DOWN: the parent sets them with an attribute or a property (`this.refs.child.value = v.x`, typed). A component never assigns its own props; to ask for a change it `emit`s an event.
43
+ - Events go UP: `emit` does not bubble; the parent handles it with `data-on-<event>` on the child tag, casting with `EmitEvent<Child, "event-name">`.
44
+ - Data needed several levels away: share a `createStore` module and `this.bind(store, fn)`; do not forward props or re-emit events through middle components (lint rules `prop-forwarding`, `emit-forwarding`).
45
+ - Writing form values back: only assign `input.value` when it differs (`if (input.value !== v.value) input.value = v.value`), otherwise the caret jumps.
46
+
47
+ ## Minimal example
48
+ ```html
49
+ <!-- counter.html -->
50
+ <p>Count: {{ count }} (double: {{ double }})</p>
51
+ <button type="button" data-on-click="inc">+1</button>
52
+ <p data-if="big">A lot!</p>
53
+ ```
54
+ ```ts
55
+ // counter.ts
56
+ import { Component, define } from "@nativele/ts-dom";
57
+ import tpl from "./counter.html.js";
58
+ class MyCounter extends Component(tpl, {
59
+ state: { count: 0 },
60
+ derive: { double: (s) => s.count * 2, big: (s) => s.count >= 10 },
61
+ }) {
62
+ inc() { this.set((s) => ({ count: s.count + 1 })); }
63
+ }
64
+ define("my-counter", MyCounter);
65
+ ```
66
+
67
+ ## Template without a component
68
+ `const view = tpl.instantiate()` returns `{ root: DocumentFragment, refs, lists, text, show }` typed like a component, for pieces created in code. No host: `data-on-*` is inert (use `refs.x.addEventListener`) and `<slot>` is not projected.
69
+
70
+ ## Setup
71
+ ```sh
72
+ npm i -D typescript && npm i @nativele/ts-dom
73
+ npx ts-dom gen && npx tsc # tsconfig: module/moduleResolution NodeNext (or bundler), lib includes DOM, strict
74
+ ```
75
+ In a browser without a bundler map the bare name once: `<script type="importmap">{"imports":{"@nativele/ts-dom":"/node_modules/@nativele/ts-dom/dist/index.mjs"}}</script>`; relative imports in your own code need `.js` extensions. With Vite, `@nativele/ts-dom/vite` is optional.
76
+
77
+ ## Pitfalls
78
+ - Import templates with `./x.html.js`, not `./x.html`. Run `ts-dom gen` before `tsc` (it creates `x.html.ts` and `ts-dom-tags.ts`; both are generated, do not edit or commit).
79
+ - Exports: `export class` the component to get typed `refs.child` in parent templates (`define("tag", Class)` is scanned).
80
+ - `data-if` content is hidden until the name is truthy; `{{ hole }}` stays empty until filled.
81
+ - `slotchange()` is Light-DOM only; with `shadow: true` use the native `slotchange` event.
82
+
83
+ ## Docs
84
+ - [README](README.md): full guide. [README.zh-TW.md](README.zh-TW.md): same in Traditional Chinese.
85
+ - `npx ts-dom rules`: every rule id with a description.
package/package.json CHANGED
@@ -1,6 +1,94 @@
1
1
  {
2
2
  "name": "@nativele/ts-dom",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.1",
4
+ "description": "A TypeScript-driven, HTML-first UI library. Bind HTML to TypeScript: HTML describes the structure, TypeScript owns behavior and state, and the DOM is the single source of truth. Fully typed, no virtual DOM, no bundler required.",
5
+ "keywords": [
6
+ "html",
7
+ "typescript",
8
+ "custom-elements",
9
+ "web-components",
10
+ "dom",
11
+ "template",
12
+ "vite",
13
+ "codegen"
14
+ ],
15
+ "license": "MIT",
16
+ "author": "Nativele contributors",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/Pixmi/nativele.git"
20
+ },
21
+ "homepage": "https://github.com/Pixmi/nativele#readme",
22
+ "bugs": {
23
+ "url": "https://github.com/Pixmi/nativele/issues"
24
+ },
25
+ "type": "module",
26
+ "main": "./dist/index.mjs",
27
+ "module": "./dist/index.mjs",
28
+ "types": "./dist/index.d.mts",
29
+ "bin": {
30
+ "ts-dom": "./dist/cli.mjs"
31
+ },
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.mts",
35
+ "default": "./dist/index.mjs"
36
+ },
37
+ "./vite": {
38
+ "types": "./dist/vite-plugin.d.mts",
39
+ "default": "./dist/vite-plugin.mjs"
40
+ },
41
+ "./oxlint": {
42
+ "types": "./dist/oxlint-plugin.d.mts",
43
+ "default": "./dist/oxlint-plugin.mjs"
44
+ },
45
+ "./package.json": "./package.json"
46
+ },
47
+ "files": [
48
+ "dist",
49
+ "README.md",
50
+ "README.zh-TW.md",
51
+ "llms.txt",
52
+ "LICENSE",
53
+ "CHANGELOG.md"
54
+ ],
55
+ "engines": {
56
+ "node": ">=20"
57
+ },
58
+ "scripts": {
59
+ "dev": "vp dev",
60
+ "build": "vp build",
61
+ "gen": "node src/cli.ts gen examples test browser",
62
+ "check": "npm run gen && tsc",
63
+ "test": "vp test run test/",
64
+ "lint": "vp lint",
65
+ "lint:html": "node src/cli.ts lint examples",
66
+ "prepack": "vp pack --publint --attw",
67
+ "build:lib": "vp pack",
68
+ "test:browser": "vp test run browser/",
69
+ "verify:pack": "vp pack --publint --attw",
70
+ "smoke": "bash scripts/consumer-smoke.sh",
71
+ "smoke:no-bundler": "bash scripts/no-bundler-smoke.sh"
72
+ },
73
+ "dependencies": {
74
+ "parse5": "^8.1.0"
75
+ },
76
+ "devDependencies": {
77
+ "@arethetypeswrong/core": "^0.18.5",
78
+ "@types/node": "^26.6.6",
79
+ "happy-dom": "^21.0.4",
80
+ "playwright-core": "^1.64.1",
81
+ "publint": "^0.3.25",
82
+ "typescript": "^7.0.2",
83
+ "vite-plus": "^1.4.0"
84
+ },
85
+ "sideEffects": [
86
+ "./examples/**",
87
+ "./browser/**",
88
+ "./test/**"
89
+ ],
90
+ "publishConfig": {
91
+ "access": "public",
92
+ "provenance": true
93
+ }
94
+ }