@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/CHANGELOG.md +63 -0
- package/LICENSE +21 -0
- package/README.md +881 -2
- package/README.zh-TW.md +887 -0
- package/dist/a11y-DZvViTVA.mjs +649 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +112 -0
- package/dist/holes-DTBtTZF_.mjs +15 -0
- package/dist/index.d.mts +242 -0
- package/dist/index.mjs +728 -0
- package/dist/lint-B7SIRo0c.mjs +356 -0
- package/dist/oxlint-plugin.d.mts +99 -0
- package/dist/oxlint-plugin.mjs +59 -0
- package/dist/vite-plugin.d.mts +40 -0
- package/dist/vite-plugin.mjs +61 -0
- package/llms.txt +85 -0
- package/package.json +92 -4
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { d as listHtml, i as CodegenError, n as checkA11y, p as watchAll, r as CODEGEN_RULES, s as generateAll, t as A11Y_RULES } from "./a11y-DZvViTVA.mjs";
|
|
3
|
+
import { c as lintUsage, n as UNUSED_MARKUP_RULES, o as formatIssue, s as lintDataFlow, t as DATA_FLOW_RULES } from "./lint-B7SIRo0c.mjs";
|
|
4
|
+
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
5
|
+
import { join, relative } from "node:path";
|
|
6
|
+
// src/cli.ts
|
|
7
|
+
const HELP = `ts-dom <command> [dirs...] [flags]
|
|
8
|
+
|
|
9
|
+
Commands
|
|
10
|
+
gen [dirs...] Generate x.html.ts for every x.html, plus ts-dom-tags.ts (default dir: src)
|
|
11
|
+
--watch keep watching and regenerate on change
|
|
12
|
+
--include=<glob> only these templates (repeatable; relative to each dir, ** crosses folders)
|
|
13
|
+
--exclude=<glob> leave these templates out (repeatable)
|
|
14
|
+
--whitespace=collapse collapse whitespace in text and drop line-break-only text between tags
|
|
15
|
+
lint [dirs...] Check templates against the code that uses them (default dir: src)
|
|
16
|
+
--a11y also check ARIA / accessibility (warnings)
|
|
17
|
+
--strict exit with code 1 on warnings too (errors always do)
|
|
18
|
+
--json print the issues as JSON instead of text
|
|
19
|
+
rules List every rule id with its description
|
|
20
|
+
|
|
21
|
+
Output format: file:line:col severity [rule-id] message. Each message says what is wrong and how to fix it.
|
|
22
|
+
Exit code: 0 = clean, 1 = errors found (or warnings with --strict).
|
|
23
|
+
`;
|
|
24
|
+
function tsFiles(dir) {
|
|
25
|
+
return readdirSync(dir).flatMap((n) => {
|
|
26
|
+
if (n === "node_modules") return [];
|
|
27
|
+
const f = join(dir, n);
|
|
28
|
+
if (statSync(f).isDirectory()) return tsFiles(f);
|
|
29
|
+
return n.endsWith(".ts") && !n.endsWith(".d.ts") && !n.endsWith(".html.ts") && n !== "ts-dom-tags.ts" ? [f] : [];
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function printRules() {
|
|
33
|
+
const groups = [
|
|
34
|
+
[
|
|
35
|
+
"gen",
|
|
36
|
+
"reported by `ts-dom gen` while analyzing a template (errors stop code generation)",
|
|
37
|
+
CODEGEN_RULES
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
"lint: unused-markup",
|
|
41
|
+
"errors from `ts-dom lint` / oxlint rule ts-dom/unused-markup",
|
|
42
|
+
UNUSED_MARKUP_RULES
|
|
43
|
+
],
|
|
44
|
+
[
|
|
45
|
+
"lint: data-flow",
|
|
46
|
+
"warnings from `ts-dom lint` / oxlint rule ts-dom/data-flow",
|
|
47
|
+
DATA_FLOW_RULES
|
|
48
|
+
],
|
|
49
|
+
[
|
|
50
|
+
"lint: a11y",
|
|
51
|
+
"warnings from `ts-dom lint --a11y` / oxlint rule ts-dom/a11y",
|
|
52
|
+
Object.fromEntries(Object.entries(A11Y_RULES).map(([k, v]) => [k, v.split(". Fix:")[0]]))
|
|
53
|
+
]
|
|
54
|
+
];
|
|
55
|
+
for (const [name, note, rules] of groups) {
|
|
56
|
+
console.log(`\n${name}: ${note}`);
|
|
57
|
+
for (const [id, desc] of Object.entries(rules)) console.log(` ${id.padEnd(24)} ${desc}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const [cmd, ...args] = process.argv.slice(2);
|
|
61
|
+
const flags = new Set(args.filter((a) => a.startsWith("--")));
|
|
62
|
+
const globs = (name) => args.filter((a) => a.startsWith(`--${name}=`)).map((a) => a.slice(name.length + 3));
|
|
63
|
+
const generate = {
|
|
64
|
+
include: globs("include"),
|
|
65
|
+
exclude: globs("exclude"),
|
|
66
|
+
whitespace: flags.has("--whitespace=collapse") ? "collapse" : "preserve"
|
|
67
|
+
};
|
|
68
|
+
const dirs = args.filter((a) => !a.startsWith("--"));
|
|
69
|
+
if (!dirs.length) dirs.push("src");
|
|
70
|
+
try {
|
|
71
|
+
if (cmd === "gen") {
|
|
72
|
+
let failed = false;
|
|
73
|
+
for (const d of dirs) try {
|
|
74
|
+
generateAll(d, generate);
|
|
75
|
+
} catch (e) {
|
|
76
|
+
if (!(e instanceof CodegenError) || !flags.has("--watch")) throw e;
|
|
77
|
+
console.error(e.message);
|
|
78
|
+
failed = true;
|
|
79
|
+
}
|
|
80
|
+
if (flags.has("--watch")) {
|
|
81
|
+
console.log(`[ts-dom] watching ${dirs.join(", ")}${failed ? " (the first generation had errors)" : ""}`);
|
|
82
|
+
watchAll(dirs, {
|
|
83
|
+
...generate,
|
|
84
|
+
onGenerate: (f) => console.log(`[ts-dom] generated ${f}`),
|
|
85
|
+
onError: (m) => console.error(m)
|
|
86
|
+
});
|
|
87
|
+
await new Promise(() => {});
|
|
88
|
+
}
|
|
89
|
+
} else if (cmd === "lint") {
|
|
90
|
+
const files = dirs.flatMap(tsFiles);
|
|
91
|
+
const issues = [...lintUsage(dirs, files), ...lintDataFlow(dirs, files)];
|
|
92
|
+
if (flags.has("--a11y")) for (const dir of dirs) for (const f of listHtml(dir)) for (const d of checkA11y(readFileSync(f, "utf8"))) issues.push({
|
|
93
|
+
file: relative(process.cwd(), f),
|
|
94
|
+
...d
|
|
95
|
+
});
|
|
96
|
+
issues.sort((x, y) => x.file.localeCompare(y.file) || x.line - y.line || x.col - y.col);
|
|
97
|
+
if (flags.has("--json")) console.log(JSON.stringify({ issues }, null, 2));
|
|
98
|
+
else for (const i of issues) (i.severity === "error" ? console.error : console.warn)(formatIssue(i));
|
|
99
|
+
if (issues.filter((i) => i.severity === "error").length || flags.has("--strict") && issues.length) process.exitCode = 1;
|
|
100
|
+
if (!flags.has("--json") && !issues.length) console.log("ts-dom lint: no issues");
|
|
101
|
+
} else if (cmd === "rules") printRules();
|
|
102
|
+
else {
|
|
103
|
+
console.log(HELP);
|
|
104
|
+
if (cmd && cmd !== "--help" && cmd !== "-h" && cmd !== "help") process.exitCode = 1;
|
|
105
|
+
}
|
|
106
|
+
} catch (e) {
|
|
107
|
+
if (e instanceof CodegenError) {
|
|
108
|
+
console.error(e.message);
|
|
109
|
+
process.exitCode = 1;
|
|
110
|
+
} else throw e;
|
|
111
|
+
}
|
|
112
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/lib/reserved.ts
|
|
2
|
+
/** Names that would override built-in DOM members or ts-dom's own members. Shared by the runtime and codegen (must not depend on Node or the DOM). */
|
|
3
|
+
const RESERVED_NAMES = new Set(`remove click focus blur append prepend before after replaceWith replaceChildren scroll scrollTo scrollBy scrollIntoView animate attachShadow closest matches contains normalize cloneNode dispatchEvent addEventListener removeEventListener getAttribute setAttribute hasAttribute removeAttribute toggleAttribute querySelector querySelectorAll insertBefore appendChild removeChild replaceChild requestFullscreen showPopover hidePopover togglePopover checkVisibility getBoundingClientRect attachInternals constructor
|
|
4
|
+
refs lists text root state props set batch render show bind on emit onCleanup connected disconnected slotchange assignedNodes connectedCallback disconnectedCallback attributeChangedCallback`.split(/\s+/));
|
|
5
|
+
/** The HTML attribute name for a camelCase prop name: userName -> user-name */
|
|
6
|
+
const attrNameOf = (prop) => prop.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
|
7
|
+
// src/lib/holes.ts
|
|
8
|
+
/** The {{ hole }} syntax: only a single identifier is allowed, no expressions or paths. Shared by the runtime and codegen. */
|
|
9
|
+
const HOLE_ANY = /\{\{([\s\S]*?)\}\}/g;
|
|
10
|
+
const IDENT = /^[A-Za-z_$][\w$]*$/;
|
|
11
|
+
function holeName(inner) {
|
|
12
|
+
const s = inner.trim();
|
|
13
|
+
return IDENT.test(s) ? s : null;
|
|
14
|
+
}
|
|
15
|
+
export { attrNameOf as a, RESERVED_NAMES as i, IDENT as n, holeName as r, HOLE_ANY as t };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
type TextValue = string | number | boolean | null | undefined;
|
|
2
|
+
interface ListSchema {
|
|
3
|
+
refs: Record<string, Element>;
|
|
4
|
+
text: string;
|
|
5
|
+
ifs: string;
|
|
6
|
+
}
|
|
7
|
+
type TextOf<N extends string> = { [K in N]: TextValue; };
|
|
8
|
+
/** The per-item context passed to update(). */
|
|
9
|
+
interface ItemContext<L extends ListSchema> {
|
|
10
|
+
readonly el: Element;
|
|
11
|
+
readonly refs: L["refs"];
|
|
12
|
+
readonly text: TextOf<L["text"]>;
|
|
13
|
+
/** Maps to a data-if inside the item */
|
|
14
|
+
show(name: L["ifs"], on: boolean): void;
|
|
15
|
+
}
|
|
16
|
+
type Update<L extends ListSchema, T> = (item: T, ctx: ItemContext<L>) => void;
|
|
17
|
+
interface ReplaceOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Skip update() when the item is the same reference as last time. Default: false.
|
|
20
|
+
* Only safe when you use immutable updates (a new object for every change); if you mutate objects in place the view keeps showing the old values.
|
|
21
|
+
*/
|
|
22
|
+
skipUnchanged?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A one-dimensional keyed list. Reordering uses `moveBefore` where the browser has it, so a moved item (a component, a playing video, a focused input) is not disconnected. It only compares keys, never subtrees;
|
|
26
|
+
* the content of an item is written explicitly by the user's update callback.
|
|
27
|
+
*/
|
|
28
|
+
export declare class List<L extends ListSchema = ListSchema> {
|
|
29
|
+
#private;
|
|
30
|
+
private readonly anchor;
|
|
31
|
+
private readonly proto;
|
|
32
|
+
/** Called when a new item is created (the component uses it to register data-on-* inside the item) */
|
|
33
|
+
private readonly onCreate?;
|
|
34
|
+
constructor(anchor: Comment, proto: Element,
|
|
35
|
+
/** Called when a new item is created (the component uses it to register data-on-* inside the item) */
|
|
36
|
+
onCreate?: ((el: Element) => void) | undefined);
|
|
37
|
+
/** From any node inside an item, find the key and data of the item it belongs to (used by event handlers). */
|
|
38
|
+
itemOf(node: Node | null): {
|
|
39
|
+
key: PropertyKey;
|
|
40
|
+
item: unknown;
|
|
41
|
+
} | undefined;
|
|
42
|
+
get size(): number;
|
|
43
|
+
has(key: PropertyKey): boolean;
|
|
44
|
+
get(key: PropertyKey): ItemContext<L> | undefined;
|
|
45
|
+
/** Add or update a single item; a new item is appended at the end. */
|
|
46
|
+
upsert<T>(key: PropertyKey, item: T, update: Update<L, T>): void;
|
|
47
|
+
remove(key: PropertyKey): boolean;
|
|
48
|
+
clear(): void;
|
|
49
|
+
/** Keyed reconciliation: remove extra items, create missing ones, reorder, and call update() for every item. */
|
|
50
|
+
replace<T>(items: readonly T[], key: (item: T) => PropertyKey, update: Update<L, T>, opts?: ReplaceOptions): void;
|
|
51
|
+
}
|
|
52
|
+
/** What `bind` needs from a store: the current value and a subscription. A read-only source can be bound too. */
|
|
53
|
+
type ReadableStore<T> = Pick<Store<T>, "get" | "subscribe">;
|
|
54
|
+
interface Store<T> {
|
|
55
|
+
get(): T;
|
|
56
|
+
set(next: T | ((prev: T) => T)): void;
|
|
57
|
+
subscribe(fn: (value: T) => void): () => void;
|
|
58
|
+
}
|
|
59
|
+
/** A tiny store: no dependency tracking, only get / set / subscribe. */
|
|
60
|
+
export declare function createStore<T>(initial: T): Store<T>;
|
|
61
|
+
/** The description of a template's structure, produced by codegen. */
|
|
62
|
+
interface Schema {
|
|
63
|
+
refs: Record<string, Element>;
|
|
64
|
+
lists: Record<string, ListSchema>;
|
|
65
|
+
/** Names of the {{ hole }}s */
|
|
66
|
+
text: string;
|
|
67
|
+
/** Method names referenced by data-on-* */
|
|
68
|
+
events: string;
|
|
69
|
+
ifs: string;
|
|
70
|
+
slots: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A template's parsed markup, written by codegen: a string is a text node, `["#comment", text]` a comment,
|
|
74
|
+
* and `[tag, attributes, children, namespace?]` an element (a `<template>` element's children become its content).
|
|
75
|
+
*/
|
|
76
|
+
type TemplateNode = string | readonly [tag: string, attrs: Readonly<Record<string, string>>, children: readonly TemplateNode[], ns?: string] | readonly ["#comment", string];
|
|
77
|
+
/** A template built without a component: the nodes, and the typed handles to the places the template names. */
|
|
78
|
+
interface TemplateInstance<S extends Schema> {
|
|
79
|
+
/** The built nodes; insert it (or its children) where it should show. */
|
|
80
|
+
readonly root: DocumentFragment;
|
|
81
|
+
/** The elements marked with `data-ref`. */
|
|
82
|
+
readonly refs: S["refs"];
|
|
83
|
+
/** The `data-each` lists. */
|
|
84
|
+
readonly lists: { [K in keyof S["lists"]]: List<S["lists"][K]>; };
|
|
85
|
+
/** The `{{ name }}` holes: assigning a value updates every place the hole appears. */
|
|
86
|
+
readonly text: TextOf<S["text"]>;
|
|
87
|
+
/** Shows or hides the `data-if` elements of that name; hidden until shown. */
|
|
88
|
+
show(name: S["ifs"], on: boolean): void;
|
|
89
|
+
}
|
|
90
|
+
interface Template<S extends Schema = Schema> {
|
|
91
|
+
/** Builds a fresh copy of the markup with DOM calls only, so no HTML string is parsed and Trusted Types has nothing to block. */
|
|
92
|
+
build(): DocumentFragment;
|
|
93
|
+
/**
|
|
94
|
+
* Builds the template and wires up `refs`, `lists`, `text` and `show` without a component or custom element, e.g. for a menu item created in code.
|
|
95
|
+
* There is no host element, so `data-on-*` attributes do nothing here (listen on `refs`), and `<slot>` is not projected.
|
|
96
|
+
*/
|
|
97
|
+
instantiate(): TemplateInstance<S>;
|
|
98
|
+
/** Type inference only; does not exist at runtime. */
|
|
99
|
+
readonly __schema?: S;
|
|
100
|
+
}
|
|
101
|
+
type El<K extends string> = K extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[K] : HTMLElement;
|
|
102
|
+
export declare function template<S extends Schema = Schema>(nodes: readonly TemplateNode[]): Template<S>;
|
|
103
|
+
interface ComponentOptions {
|
|
104
|
+
/**
|
|
105
|
+
* Default false: render into the element itself (Light DOM); <slot> content is moved by the library at creation time.
|
|
106
|
+
* true: use Shadow DOM: styles are isolated and <slot> is the browser's native behavior.
|
|
107
|
+
*/
|
|
108
|
+
shadow?: boolean;
|
|
109
|
+
}
|
|
110
|
+
/** Event declaration: put `event<DetailType>()` in the `emits` option; the type is only used to check emit() and receivers. */
|
|
111
|
+
interface EventDecl<T = void> {
|
|
112
|
+
/** Default false: the event only reaches the child's own tag, so the handler is always written on `<child data-on-xxx>` in the parent's HTML. */
|
|
113
|
+
readonly bubbles?: boolean;
|
|
114
|
+
/** Type inference only; does not exist at runtime. */
|
|
115
|
+
readonly "~detail"?: T;
|
|
116
|
+
}
|
|
117
|
+
export declare function event<T = void>(opts?: {
|
|
118
|
+
bubbles?: boolean;
|
|
119
|
+
}): EventDecl<T>;
|
|
120
|
+
type DetailOf<D> = D extends EventDecl<infer T> ? T : never;
|
|
121
|
+
type EmitArgs<D> = [DetailOf<D>] extends [void] ? [detail?: undefined] : [detail: DetailOf<D>];
|
|
122
|
+
/** The data passed to render(): state, props and derived values merged. */
|
|
123
|
+
type View<D, R> = Readonly<D> & Readonly<R>;
|
|
124
|
+
interface DataOptions<St extends object, P extends object, R, E extends Record<string, EventDecl<unknown>>> extends ComponentOptions {
|
|
125
|
+
/** Data owned by the component and changed only through set(). Fields named like a {{ hole }} or data-if in the HTML bind automatically. */
|
|
126
|
+
state?: St;
|
|
127
|
+
/**
|
|
128
|
+
* Data provided by the parent (read-only): the parent sets it with an HTML attribute (`user-name="Ada"`) or a JS property (`el.userName = "Ada"`).
|
|
129
|
+
* The default value decides the type and how attributes are converted (string / number / boolean); arrays and objects can only be passed as JS properties.
|
|
130
|
+
* A component must not modify its props; use emit() to notify the parent.
|
|
131
|
+
*/
|
|
132
|
+
props?: P;
|
|
133
|
+
/**
|
|
134
|
+
* Derived values: computed from state and props, usable directly by {{ holes }}, data-if and render().
|
|
135
|
+
* Recomputed after every data change (there is no dependency tracking).
|
|
136
|
+
*/
|
|
137
|
+
derive?: { [K in keyof R]: (s: Readonly<St & P>) => R[K]; };
|
|
138
|
+
/** The events this component emits. */
|
|
139
|
+
emits?: E;
|
|
140
|
+
}
|
|
141
|
+
type Keys<St, P, R> = (keyof St | keyof P | keyof R) & string;
|
|
142
|
+
type MissingBindings<S extends Schema, St, P, R> = Exclude<S["text"] | S["ifs"], Keys<St, P, R>>;
|
|
143
|
+
type Conflicts<St, P, R> = (keyof St & keyof P) | (keyof St & keyof R) | (keyof P & keyof R);
|
|
144
|
+
type ReservedProps<P> = Extract<keyof P, keyof HTMLElement | "refs" | "lists" | "text" | "root" | "state" | "props" | "set" | "batch" | "render" | "show" | "bind" | "on" | "emit">;
|
|
145
|
+
type NativeEmits<E> = Extract<keyof E, keyof HTMLElementEventMap>;
|
|
146
|
+
type Problems<S extends Schema, St, P, R, E> = ([keyof St | keyof P | keyof R] extends [never] ? unknown : [MissingBindings<S, St, P, R>] extends [never] ? unknown : {
|
|
147
|
+
missingStateKeys: MissingBindings<S, St, P, R>;
|
|
148
|
+
}) & ([Conflicts<St, P, R>] extends [never] ? unknown : {
|
|
149
|
+
conflictingKeys: Conflicts<St, P, R>;
|
|
150
|
+
}) & ([ReservedProps<P>] extends [never] ? unknown : {
|
|
151
|
+
reservedPropNames: ReservedProps<P>;
|
|
152
|
+
}) & ([NativeEmits<E>] extends [never] ? unknown : {
|
|
153
|
+
nativeEventNames: NativeEmits<E>;
|
|
154
|
+
});
|
|
155
|
+
type IsClean<T> = unknown extends T ? true : false;
|
|
156
|
+
type Lists<S extends Schema> = { [K in keyof S["lists"]]: List<S["lists"][K]>; };
|
|
157
|
+
/** The second argument received by a data-on-* handler method. */
|
|
158
|
+
interface EventHit<Item = unknown> {
|
|
159
|
+
/** The element that carries the data-on-* attribute */
|
|
160
|
+
readonly el: Element;
|
|
161
|
+
/** When the event happened inside a list item: the key of that item */
|
|
162
|
+
readonly key?: PropertyKey;
|
|
163
|
+
/** When the event happened inside a list item: the data last passed to update() for that item. Annotate the handler, e.g. `hit: EventHit<Todo>`, to type it. */
|
|
164
|
+
readonly item?: Item;
|
|
165
|
+
}
|
|
166
|
+
interface ComponentInstance<S extends Schema> extends HTMLElement {
|
|
167
|
+
/** Type inference only (define() uses it to check that data-on-* methods exist); does not exist at runtime. */
|
|
168
|
+
readonly "~events"?: S["events"];
|
|
169
|
+
readonly refs: S["refs"];
|
|
170
|
+
readonly lists: Lists<S>;
|
|
171
|
+
/** Maps to {{ name }}: assigning a value updates every place the hole appears. */
|
|
172
|
+
readonly text: TextOf<S["text"]>;
|
|
173
|
+
readonly root: ShadowRoot | HTMLElement;
|
|
174
|
+
/** Maps to data-if: hidden by default (removed from the DOM and replaced by a comment); shown when true. */
|
|
175
|
+
show(name: S["ifs"], on: boolean): void;
|
|
176
|
+
/** Subscribe to a store: calls fn immediately once, and unsubscribes automatically when the element is removed. */
|
|
177
|
+
bind<T>(store: ReadableStore<T>, fn: (value: T) => void): void;
|
|
178
|
+
/** Add an event listener that is removed automatically when the element is removed. Returns a function to remove it manually. */
|
|
179
|
+
on<K extends keyof HTMLElementEventMap>(target: HTMLElement, type: K, fn: (e: HTMLElementEventMap[K]) => void, opts?: AddEventListenerOptions): () => void;
|
|
180
|
+
on(target: EventTarget, type: string, fn: (e: Event) => void, opts?: AddEventListenerOptions): () => void;
|
|
181
|
+
/** Register a cleanup function that runs when the element is removed. */
|
|
182
|
+
onCleanup(fn: () => void): void;
|
|
183
|
+
/** The nodes currently assigned to the slot (fallback content excluded). In Shadow mode this is the native `slot.assignedNodes()`. */
|
|
184
|
+
assignedNodes(name: S["slots"]): Node[];
|
|
185
|
+
connected(): void;
|
|
186
|
+
disconnected(): void;
|
|
187
|
+
/** Light mode: called when slot content changes after creation (the initial assignment does not trigger it). */
|
|
188
|
+
slotchange(name: S["slots"]): void;
|
|
189
|
+
}
|
|
190
|
+
/** Members added when the `state` / `props` / `derive` / `emits` options are used. */
|
|
191
|
+
interface StatefulInstance<S extends Schema, St extends object, P extends object, R, E extends Record<string, EventDecl<unknown>>> extends ComponentInstance<S> {
|
|
192
|
+
/** The component's own data (read-only). Use set() to change it. */
|
|
193
|
+
readonly state: Readonly<St>;
|
|
194
|
+
/** Data provided by the parent (read-only). Do not assign it inside the component; use emit() to notify the parent. */
|
|
195
|
+
readonly props: Readonly<P>;
|
|
196
|
+
/**
|
|
197
|
+
* The only way to change state: shallow-merges the patch, and does nothing when no field actually changed.
|
|
198
|
+
* When something changed it synchronously updates the same-named {{ holes }} and data-if, then calls render(). It cannot change props.
|
|
199
|
+
*/
|
|
200
|
+
set(patch: Partial<St> | ((s: Readonly<St>) => Partial<St>)): void;
|
|
201
|
+
/** Coalesce several set() calls and prop changes: the view is updated and render() is called once, after fn returns. */
|
|
202
|
+
batch(fn: () => void): void;
|
|
203
|
+
/**
|
|
204
|
+
* Override it to handle updates that automatic binding cannot do (lists, attributes, focus, ...).
|
|
205
|
+
* `view` is state, props and derived values merged; `changed` holds the fields that changed (all of them on the first render).
|
|
206
|
+
*/
|
|
207
|
+
render(view: View<St & P, R>, changed: ReadonlySet<keyof St | keyof P>): void;
|
|
208
|
+
/** Emit an event declared in `emits` (it does not bubble by default and only reaches this element itself). */
|
|
209
|
+
emit<K extends keyof E & string>(name: K, ...detail: EmitArgs<E[K]>): void;
|
|
210
|
+
/** Type inference only (EmitEvent uses it to get the detail type of an event); does not exist at runtime. */
|
|
211
|
+
readonly "~emits"?: { [K in keyof E]: DetailOf<E[K]>; };
|
|
212
|
+
}
|
|
213
|
+
/** The type of an event emitted by a component: `EmitEvent<FormField, "change-value">` is `CustomEvent<{ value: string }>`. */
|
|
214
|
+
type EmitEvent<I, N extends string> = I extends {
|
|
215
|
+
readonly "~emits"?: infer M;
|
|
216
|
+
} ? N extends keyof NonNullable<M> ? CustomEvent<NonNullable<M>[N]> : never : never;
|
|
217
|
+
export declare function Component<S extends Schema>(tpl: Template<S>, opts?: ComponentOptions & {
|
|
218
|
+
state?: never;
|
|
219
|
+
props?: never;
|
|
220
|
+
derive?: never;
|
|
221
|
+
emits?: never;
|
|
222
|
+
}): new () => ComponentInstance<S>;
|
|
223
|
+
/**
|
|
224
|
+
* With the `state` / `props` / `derive` / `emits` options, {{ holes }} and data-if in the HTML bind to same-named data.
|
|
225
|
+
* When something is wrong the return type becomes an object type describing the problem, so
|
|
226
|
+
* `class X extends Component(...)` fails in tsc and lists the names:
|
|
227
|
+
* `missingStateKeys` (the HTML uses a name that does not exist), `conflictingKeys` (state / props / derive share a name),
|
|
228
|
+
* `reservedPropNames` (a prop shares a name with HTMLElement or a ts-dom member), `nativeEventNames` (emits shares a name with a native event).
|
|
229
|
+
* (The check lives in the return type, not in the parameter: in the parameter it prevents TypeScript from inferring the type of derive.)
|
|
230
|
+
*/
|
|
231
|
+
export declare function Component<S extends Schema, St extends object = Record<never, never>, P extends object = Record<never, never>, R = Record<never, never>, E extends Record<string, EventDecl<unknown>> = Record<never, never>>(tpl: Template<S>, opts: DataOptions<St, P, R, E>): IsClean<Problems<S, St, P, R, E>> extends true ? new () => StatefulInstance<S, St, P, R, E> & P : Problems<S, St, P, R, E>;
|
|
232
|
+
type CheckHandlers<I> = I extends {
|
|
233
|
+
readonly "~events"?: infer E;
|
|
234
|
+
} ? [Exclude<NonNullable<E>, keyof I>] extends [never] ? unknown : {
|
|
235
|
+
missingHandlers: Exclude<NonNullable<E>, keyof I>;
|
|
236
|
+
} : unknown;
|
|
237
|
+
/**
|
|
238
|
+
* Register a custom element. Also checks types: every method name referenced by data-on-* in the HTML must exist on the
|
|
239
|
+
* component as a public method, otherwise tsc reports an error that lists the missing names (`missingHandlers`).
|
|
240
|
+
*/
|
|
241
|
+
export declare function define<C extends abstract new (...args: never[]) => HTMLElement>(name: string, ctor: C & CheckHandlers<InstanceType<C>>): void;
|
|
242
|
+
export type { ComponentInstance, ComponentOptions, DataOptions, El, EmitEvent, EventDecl, EventHit, ItemContext, ListSchema, ReadableStore, ReplaceOptions, Schema, StatefulInstance, Store, Template, TemplateInstance, TextOf, TextValue, Update, View };
|