@geektech/tsone 0.0.1 → 0.1.0

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.
Files changed (40) hide show
  1. package/README-zh.md +405 -0
  2. package/README.md +288 -32
  3. package/dist/core/animation/TransitionGroup.d.ts +7 -0
  4. package/dist/core/animation/index.d.ts +2 -0
  5. package/dist/core/animation/list-animation-controller.d.ts +16 -0
  6. package/dist/core/animation/transition-group-strategy.d.ts +15 -0
  7. package/dist/core/animation/types.d.ts +25 -0
  8. package/dist/core/app.d.ts +30 -8
  9. package/dist/core/component/base.d.ts +18 -1
  10. package/dist/core/component/index.d.ts +2 -2
  11. package/dist/core/document.d.ts +32 -0
  12. package/dist/core/form.d.ts +24 -0
  13. package/dist/core/index.d.ts +4 -0
  14. package/dist/core/model.d.ts +18 -0
  15. package/dist/core/reactive/types.d.ts +5 -0
  16. package/dist/core/reactive.d.ts +5 -2
  17. package/dist/core/renderer/element-strategy.d.ts +25 -0
  18. package/dist/core/renderer/types.d.ts +6 -1
  19. package/dist/core/renderer.d.ts +9 -23
  20. package/dist/core/vnode.d.ts +56 -16
  21. package/dist/{index-dgv88dz4.js → index-8wjswsye.js} +6 -2
  22. package/dist/index-8wjswsye.js.map +10 -0
  23. package/dist/index-vpx80nq5.js +32 -0
  24. package/dist/index-vpx80nq5.js.map +10 -0
  25. package/dist/{index-3j2jsdpc.js → index-wv9gyjqt.js} +858 -244
  26. package/dist/index-wv9gyjqt.js.map +25 -0
  27. package/dist/index.d.ts +3 -2
  28. package/dist/index.js +380 -20
  29. package/dist/index.js.map +8 -5
  30. package/dist/router/index.d.ts +1 -1
  31. package/dist/router/index.js +6 -4
  32. package/dist/router/index.js.map +1 -1
  33. package/dist/style/StyleManager.d.ts +1 -0
  34. package/dist/style/index.d.ts +1 -0
  35. package/dist/style/index.js +6 -2
  36. package/dist/style/index.js.map +1 -1
  37. package/dist/style/sheet.d.ts +13 -0
  38. package/package.json +4 -14
  39. package/dist/index-3j2jsdpc.js.map +0 -20
  40. package/dist/index-dgv88dz4.js.map +0 -10
package/README.md CHANGED
@@ -1,28 +1,44 @@
1
1
  # TSone
2
2
 
3
- 轻量级纯 TypeScript 前端框架,提供响应式系统、类组件、策略化渲染和路由能力。
3
+ English | [简体中文](./README-zh.md)
4
4
 
5
- ## 特性
5
+ A lightweight frontend framework written entirely in TypeScript, with
6
+ reactivity, class-based components, strategy-driven rendering, and routing.
6
7
 
7
- - 纯 TypeScript 实现,公开 API 提供类型定义
8
- - Bun 原生工具链:安装、测试、构建、示例服务和文档服务均由 Bun 驱动
9
- - 响应式系统:`reactive`、`effect`、`computed`
10
- - 面向对象组件模型:`Component<Props, State>`、生命周期、事件、插槽
11
- - 策略模式渲染层:文本、元素、组件、插槽按 VNode 类型分发
12
- - 内置路由:`createRouter`、`RouterView`、`RouterLink`
13
- - 轻量级运行时,生产包无外部运行时依赖
8
+ ## Features
14
9
 
15
- ## 安装
10
+ - TypeScript-first implementation with typed public APIs
11
+ - Bun-native tooling for installation, testing, builds, playgrounds, and docs
12
+ - Documentation backed by a TypeScript typed-content registry
13
+ - Reactive primitives: `reactive`, `effect`, and `computed`
14
+ - Object-oriented components with `Component<Props, State>`, lifecycle hooks,
15
+ events, and slots
16
+ - Strategy-based rendering that dispatches text, elements, components, and
17
+ slots by VNode type
18
+ - Built-in routing with `createRouter`, `RouterView`, and `RouterLink`
19
+ - Lightweight runtime with no external production dependencies
20
+
21
+ ## Repository Structure
22
+
23
+ This repository is a Bun workspace monorepo with two published packages:
24
+ `packages/tsone/` contains the browser framework `@geektech/tsone`, and
25
+ `packages/tsone-cli/` contains the Bun-native development tooling
26
+ `@geektech/tsone-cli`. Standalone example projects live in the root
27
+ `playground/` directory.
28
+
29
+ ## Installation
16
30
 
17
31
  ```bash
18
- bun add @geektech/tsone
32
+ bun add @geektech/tsone @geektech/tsone-cli
19
33
  ```
20
34
 
21
35
  ```bash
22
- pnpm add @geektech/tsone
36
+ pnpm add @geektech/tsone @geektech/tsone-cli
23
37
  ```
24
38
 
25
- ## 快速开始
39
+ The framework and CLI require Bun `>=1.3.0` for the documented workflow.
40
+
41
+ ## Quick Start
26
42
 
27
43
  ```typescript
28
44
  import {
@@ -42,7 +58,7 @@ class App extends Component<Record<string, never>, AppState> {
42
58
  protected initState(): AppState {
43
59
  return {
44
60
  count: 0,
45
- version: '0.0.1',
61
+ version: '0.0.2',
46
62
  };
47
63
  }
48
64
 
@@ -72,13 +88,73 @@ class App extends Component<Record<string, never>, AppState> {
72
88
  const state = reactive({ ready: true });
73
89
  const status = computed(() => (state.ready ? 'ready' : 'pending'));
74
90
 
75
- const app = createApp({ root: App, rootElement: '#app', state });
91
+ const app = createApp({ root: App, state });
76
92
  app.mount();
77
93
 
78
94
  console.log(status.value);
79
95
  ```
80
96
 
81
- ## 路由
97
+ `createApp` uses `#app` as its default mount target, so an application can call
98
+ `app.mount()` immediately after creation. If the target is not currently in the
99
+ document, `mount()` safely skips that attempt. Pass `rootElement` only when you
100
+ need to override the default target.
101
+
102
+ ## Development Tooling
103
+
104
+ The separate `@geektech/tsone-cli` package provides `tsone dev` and
105
+ `tsone build`. Its default entry is `src/main.ts`; that module must expose the
106
+ application as `export const app`, and the value must provide
107
+ `renderHtmlDocument()`.
108
+
109
+ Create an optional `tsone.config.ts` at the project root:
110
+
111
+ The config file supports only a plain-object default export; functional or
112
+ function-valued config is not supported.
113
+
114
+ ```typescript
115
+ import { defineConfig } from '@geektech/tsone-cli';
116
+
117
+ export default defineConfig({
118
+ server: {
119
+ proxy: {
120
+ '/api': {
121
+ target: 'http://localhost:3000',
122
+ changeOrigin: true,
123
+ rewrite: (path) => path.replace(/^\/api/, ''),
124
+ },
125
+ },
126
+ },
127
+ build: { outDir: 'dist' },
128
+ });
129
+ ```
130
+
131
+ The string proxy shorthand is also supported, for example
132
+ `{ '/backend': 'http://localhost:4000' }`. Defaults are `src/main.ts`,
133
+ `127.0.0.1`, port `52211`, an empty `server.proxy`, and `dist`.
134
+
135
+ ```text
136
+ tsone dev [--host <host>] [--port <port>]
137
+ tsone build [--out-dir <path>]
138
+ ```
139
+
140
+ `dev` accepts host/port overrides and `build` accepts the output-directory
141
+ override; both `--port 3000` and `--port=3000` forms are valid. Build output
142
+ must remain a safe child directory inside the project root.
143
+
144
+ Programmatic tooling is imported from `@geektech/tsone-cli`, not from the
145
+ framework root. It exports `defineConfig`, `resolveConfig`, `startDevServer`,
146
+ and `build`. The dev server is owned by the caller, which must call
147
+ `server.stop()`; `build()` returns absolute `root` and `outDir` values plus
148
+ `assetsBuilt`.
149
+
150
+ The development server serves HTTP only. Proxy targets may use HTTP or HTTPS.
151
+ Rules use literal prefix matching with the longest match first, retain
152
+ query/body/end-to-end headers, optionally apply `changeOrigin` and `rewrite`,
153
+ and return `502 Bad Gateway` when the upstream is unreachable. CLI v1 has no
154
+ config plugins, WebSocket, HMR, SSR, functional config, `public/` copying, or
155
+ public minify/sourcemap settings.
156
+
157
+ ## Routing
82
158
 
83
159
  ```typescript
84
160
  import { Component, VNode, createApp } from '@geektech/tsone';
@@ -97,11 +173,11 @@ class Layout extends Component {
97
173
  children: [
98
174
  {
99
175
  component: RouterLink,
100
- props: { to: '/', children: ['首页'] },
176
+ props: { to: '/', children: ['Home'] },
101
177
  },
102
178
  {
103
179
  component: RouterLink,
104
- props: { to: '/users/42', children: ['用户'] },
180
+ props: { to: '/users/42', children: ['User'] },
105
181
  },
106
182
  { component: RouterView },
107
183
  ],
@@ -113,14 +189,18 @@ const router = createRouter({
113
189
  mode: 'history',
114
190
  routes: [
115
191
  { path: '/', component: HomePage },
116
- { path: '/users/:id', component: UserPage, meta: { title: '用户详情' } },
192
+ {
193
+ path: '/users/:id',
194
+ component: UserPage,
195
+ meta: { title: 'User Details' },
196
+ },
117
197
  ],
118
198
  });
119
199
 
120
- createApp({ root: Layout, rootElement: '#app' }).use(router).mount();
200
+ createApp({ root: Layout }).use(router).mount();
121
201
  ```
122
202
 
123
- ## 开发命令
203
+ ## Development Commands
124
204
 
125
205
  ```bash
126
206
  bun install
@@ -128,23 +208,197 @@ bun test
128
208
  bun run build
129
209
  bun run dev
130
210
  bun run docs
211
+ bun run docs:build
212
+ ```
213
+
214
+ The root `playground/` directory contains two standalone projects:
215
+
216
+ ```bash
217
+ bun run dev
218
+ bun run dev:site
219
+ bun run dev:admin
220
+ ```
221
+
222
+ - `playground/official-site`: product website example
223
+ - `playground/admin-dashboard`: admin dashboard example
224
+
225
+ Documentation content is maintained in the typed-content registry. Chinese
226
+ content lives in `packages/tsone/docs/app/content/zh/`, while English content
227
+ lives in `packages/tsone/docs/app/content/en/`. Every logical route must exist
228
+ in both directories.
229
+
230
+ Chinese and English catalogs each contain exactly 14 logical routes. Add or
231
+ remove a route in both catalogs in the same change.
232
+
233
+ Content links stay locale-neutral: never write `/en/` manually. Chinese public
234
+ routes are unprefixed, while English routes use `/en/`. Browser-language
235
+ detection runs only at `/`; manual selection takes precedence and persists for
236
+ later visits.
237
+
238
+ Build the static documentation site with:
239
+
240
+ ```bash
241
+ bun run docs:build
242
+ ```
243
+
244
+ The build fails strictly for missing, extra, duplicate, empty, or
245
+ mixed-language pages.
246
+
247
+ The base HTML document shell can also be generated from `createApp`. It emits a
248
+ `#app` mount node by default; pass `rootElement` only to use a different target.
249
+ When a custom `body` is needed, pass a component or VNode rather than an HTML
250
+ string. This API renders the mount node through TSone's renderer. In a Bun or
251
+ Node static-generation environment, provide a DOM-like document first:
252
+
253
+ ```typescript
254
+ import { createApp, type StyleSheet } from '@geektech/tsone';
255
+
256
+ const styles: StyleSheet = [
257
+ {
258
+ selector: '.app',
259
+ properties: { maxWidth: '72rem' },
260
+ },
261
+ ];
262
+
263
+ const app = createApp({
264
+ root: App,
265
+ document: {
266
+ lang: 'en',
267
+ title: 'TSone App',
268
+ styles,
269
+ },
270
+ });
271
+
272
+ const html = app.renderHtmlDocument({
273
+ scripts: [{ type: 'module', src: '/assets/app.js' }],
274
+ });
131
275
  ```
132
276
 
133
- ## 公开 API
277
+ ## Public API
134
278
 
135
- 主入口 `@geektech/tsone`:
279
+ The main `@geektech/tsone` entry point exports:
136
280
 
137
281
  - `createApp(options)`
282
+ - `createApp({ root, rootProps })`
138
283
  - `Component<Props, State>`
284
+ - `TransitionGroup` / `TransitionGroupProps` / `TransitionAnimationType`
139
285
  - `VNode`
140
- - `h()` / `createComponent()` / `slot()`
286
+ - `h()` / `createComponent()` / `slot()` / `each()`
287
+ - `Tag(tag, options)` for arbitrary HTML elements
288
+ - `Div()` / `Span()` / `P()` / `Button()` / `Input()`
289
+ - `Section()` / `Main()` / `Header()` / `Footer()` / `Nav()` / `Article()` /
290
+ `Aside()`
291
+ - `H1()` through `H6()` / `Strong()` / `Em()` / `Small()` / `Pre()` /
292
+ `Code()` / `Blockquote()`
293
+ - `Ul()` / `Ol()` / `Li()` / `A()` / `Img()`
294
+ - `Form()` / `Label()` / `Textarea()` / `Select()` / `Option()`
295
+ - `Table()` / `Thead()` / `Tbody()` / `Tr()` / `Th()` / `Td()`
296
+ - `Directions` / `ModelBinding`
297
+ - `InjectionKey` and component/application `provide()` / `inject()`
298
+ - `createForm()` / `required()` / `minLength()` / `validate()`
299
+ - `createApp(options).renderHtmlDocument(options)`
300
+ - `renderHtmlDocument(options)`
301
+ - `StyleSheet` / `renderStyleSheet(styles)`
141
302
  - `reactive()` / `readonly()`
142
303
  - `effect()` / `stop()`
143
304
  - `computed()`
144
305
  - `ref()` / `isRef()` / `unref()`
145
- - `version`,当前为 `0.0.1`
306
+ - `version`, currently `0.0.2`
307
+
308
+ ## Rendering, Communication, and Forms
309
+
310
+ `directions.if` controls whether an element, component, or slot is mounted. The
311
+ node is unmounted when the condition is false:
312
+
313
+ ```typescript
314
+ {
315
+ component: ProfilePanel,
316
+ directions: { if: this.state.visible },
317
+ }
318
+ ```
319
+
320
+ `each()` creates stable keys for list items so the renderer can reuse nodes
321
+ during reordering, insertion, and removal:
322
+
323
+ ```typescript
324
+ const items = each(
325
+ this.state.users,
326
+ (user) => ({ tag: 'li', children: [user.name] }),
327
+ (user) => user.id
328
+ );
329
+ ```
330
+
331
+ `TransitionGroup` animates the enter and exit of direct keyed children:
332
+
333
+ ```typescript
334
+ {
335
+ component: TransitionGroup,
336
+ props: { tag: 'ul', type: 'fade', duration: 300 },
337
+ children: each(
338
+ this.state.items,
339
+ (item) => Li({ children: [item.label] }),
340
+ (item) => item.id
341
+ ),
342
+ }
343
+ ```
344
+
345
+ The animation types are `fade`, `slide-up`, `slide-down`, `slide-left`,
346
+ `slide-right`, and `scale`. The defaults are a `div` wrapper, `fade`, and
347
+ `300` ms with `ease` easing. Every direct child must have a unique key. Initial
348
+ children animate in, and removed children stay mounted until their exit ends.
349
+ TSone automatically disables these animations for
350
+ `prefers-reduced-motion: reduce` or when Web Animations is unavailable.
351
+ Reordering reuses and moves existing nodes without a reorder or FLIP animation.
352
+
353
+ Component events return an unsubscribe function, while component VNodes can
354
+ declare parent listeners through `emitters`:
355
+
356
+ ```typescript
357
+ const stopListening = child.on('saved', (payload) => console.log(payload));
358
+ stopListening();
359
+ // { component: Editor, emitters: { saved: (payload) => this.save(payload) } }
360
+ ```
361
+
362
+ Dependency injection resolves values from the current component, then its
363
+ parents, and finally the application instance:
364
+
365
+ ```typescript
366
+ const THEME: InjectionKey<{ mode: string }> = Symbol('theme');
367
+ app.provide(THEME, { mode: 'dark' });
368
+ const theme = this.inject(THEME, { mode: 'light' });
369
+ ```
370
+
371
+ `directions.model` supports dot-separated paths and conversion functions.
372
+ Native input, textarea, checkbox, radio, and select controls stay synchronized:
373
+
374
+ ```typescript
375
+ Input({
376
+ props: { type: 'number' },
377
+ directions: {
378
+ model: {
379
+ path: 'profile.age',
380
+ parse: (value) => Number(value),
381
+ format: (value) => String(value ?? ''),
382
+ },
383
+ },
384
+ });
385
+ ```
386
+
387
+ Validation uses pure functions and does not own error UI or submission:
388
+
389
+ ```typescript
390
+ const form = createForm(this.state, {
391
+ 'profile.name': [required('Name is required'), minLength(2)],
392
+ 'profile.age': [
393
+ validate((value) => Number(value) >= 18 || 'Age must be at least 18'),
394
+ ],
395
+ });
396
+
397
+ const result = form.validate();
398
+ form.resetErrors();
399
+ ```
146
400
 
147
- 路由入口 `@geektech/tsone/router`:
401
+ The `@geektech/tsone/router` entry point exports:
148
402
 
149
403
  - `createRouter({ routes, mode, base })`
150
404
  - `Router`
@@ -154,23 +408,25 @@ bun run docs
154
408
  - `RouteRecord`
155
409
  - `RouteLocation`
156
410
 
157
- 样式入口 `@geektech/tsone/style`:
411
+ The `@geektech/tsone/style` entry point exports:
158
412
 
159
413
  - `StyleManager`
414
+ - `StyleSheet` / `renderStyleSheet(styles)`
160
415
 
161
- ## 发布前检查
416
+ ## Pre-Publish Checklist
162
417
 
163
418
  ```bash
164
419
  bun test
165
420
  bunx tsc --noEmit
166
421
  bun run build
167
- bun pm pack --dry-run
422
+ bun pm pack --cwd packages/tsone --dry-run
168
423
  ```
169
424
 
170
- ## 贡献
425
+ ## Contributing
171
426
 
172
- 欢迎提交 Issue Pull Request。开源发布前请确保测试、类型检查和构建均通过。
427
+ Issues and pull requests are welcome. Before publishing an open-source release,
428
+ make sure the tests, type checks, and build all pass.
173
429
 
174
- ## 许可证
430
+ ## License
175
431
 
176
432
  [MIT](LICENSE)
@@ -0,0 +1,7 @@
1
+ import { Component } from '../component';
2
+ import { type TransitionGroupNode, type TransitionGroupProps } from './types';
3
+ export declare class TransitionGroup extends Component<TransitionGroupProps> {
4
+ protected initState(): object;
5
+ protected initStyles(): void;
6
+ protected render(): TransitionGroupNode;
7
+ }
@@ -0,0 +1,2 @@
1
+ export { TransitionGroup } from './TransitionGroup';
2
+ export type { TransitionAnimationType, TransitionGroupProps } from './types';
@@ -0,0 +1,16 @@
1
+ import type { TransitionGroupOptions } from './types';
2
+ export interface ListAnimationRun {
3
+ animation: Animation;
4
+ token: symbol;
5
+ keyframes: Keyframe[];
6
+ options: KeyframeAnimationOptions;
7
+ finished: Promise<'finished' | 'cancelled'>;
8
+ }
9
+ export declare class ListAnimationController {
10
+ private readonly runs;
11
+ playEnter(element: HTMLElement, options: TransitionGroupOptions): ListAnimationRun | null;
12
+ playExit(element: HTMLElement, options: TransitionGroupOptions): ListAnimationRun | null;
13
+ cancel(element: HTMLElement): void;
14
+ private play;
15
+ private canAnimate;
16
+ }
@@ -0,0 +1,15 @@
1
+ import { ElementRenderStrategy } from '../renderer/element-strategy';
2
+ import type { RenderRuntimeContext, Renderable } from '../renderer/types';
3
+ import { type TransitionGroupNode } from './types';
4
+ export declare class TransitionGroupRenderStrategy extends ElementRenderStrategy<TransitionGroupNode> {
5
+ private readonly entries;
6
+ private readonly animations;
7
+ matches(vnode: Renderable): vnode is TransitionGroupNode;
8
+ protected mountChildren(element: HTMLElement, groupVNode: TransitionGroupNode, context: RenderRuntimeContext): void;
9
+ protected updateChildren(element: HTMLElement, _oldVNode: TransitionGroupNode, newVNode: TransitionGroupNode, context: RenderRuntimeContext): void;
10
+ protected unmountChildren(element: HTMLElement, vnode: TransitionGroupNode, context: RenderRuntimeContext): void;
11
+ private playEnter;
12
+ private startExit;
13
+ private finishExit;
14
+ private placeActiveEntries;
15
+ }
@@ -0,0 +1,25 @@
1
+ import { type EventListeners, type HTMLNode, type HTMLProps, type VNode } from '../vnode';
2
+ export declare const TRANSITION_ANIMATION_TYPES: readonly ["fade", "slide-up", "slide-down", "slide-left", "slide-right", "scale"];
3
+ export type TransitionAnimationType = (typeof TRANSITION_ANIMATION_TYPES)[number];
4
+ export interface TransitionGroupOptions {
5
+ type: TransitionAnimationType;
6
+ duration: number;
7
+ }
8
+ export interface TransitionGroupProps {
9
+ tag?: string;
10
+ type?: TransitionAnimationType;
11
+ duration?: number;
12
+ elementProps?: HTMLProps;
13
+ listeners?: EventListeners;
14
+ children?: VNode[];
15
+ }
16
+ export interface TransitionGroupNode extends HTMLNode {
17
+ transitionGroup: TransitionGroupOptions;
18
+ children?: VNode[];
19
+ }
20
+ export type KeyedTransitionChild = VNode & {
21
+ key: string | number;
22
+ };
23
+ export declare function normalizeTransitionGroupProps(props: TransitionGroupProps): Required<Pick<TransitionGroupProps, 'tag' | 'type' | 'duration'>>;
24
+ export declare function validateTransitionGroupChildren(children: Array<VNode | string>): KeyedTransitionChild[];
25
+ export declare function isTransitionGroupNode(vnode: unknown): vnode is TransitionGroupNode;
@@ -1,4 +1,5 @@
1
- import { ComponentConstructor } from './component';
1
+ import { ComponentConstructor, ComponentProps, InjectionKey, InjectionResult } from './component';
2
+ import { type HtmlDocumentBody, type HtmlDocumentOptions } from './document';
2
3
  import type { Router } from '../router';
3
4
  export interface Plugin {
4
5
  install: (app: OneApp, ...args: unknown[]) => void;
@@ -6,15 +7,23 @@ export interface Plugin {
6
7
  onUpdated?: (app: OneApp) => void;
7
8
  onBeforeUnmount?: (app: OneApp) => void;
8
9
  }
9
- export interface AppOptions<TState = Record<string, unknown>, TConfig = Record<string, unknown>> {
10
+ export type AppDocumentOptions = Partial<Omit<HtmlDocumentOptions, 'body'>> & {
11
+ body?: HtmlDocumentBody;
12
+ };
13
+ export type AppDocumentRenderOptions = AppDocumentOptions;
14
+ export interface AppOptions<TState = Record<string, unknown>, TConfig = Record<string, unknown>, TRootProps extends ComponentProps = ComponentProps> {
10
15
  /** 根组件构造函数 */
11
- root?: ComponentConstructor;
12
- /** 应用挂载点 */
16
+ root?: ComponentConstructor<TRootProps>;
17
+ /** 根组件 props */
18
+ rootProps?: TRootProps;
19
+ /** 应用挂载点,默认 #app */
13
20
  rootElement?: string | Element;
14
21
  /** 全局状态 */
15
22
  state?: TState;
16
23
  /** 全局配置 */
17
24
  config?: TConfig;
25
+ /** HTML 文档壳配置,用于 dev/build 生成入口页面 */
26
+ document?: AppDocumentOptions;
18
27
  }
19
28
  export interface AppContext<TConfig = Record<string, unknown>> {
20
29
  app: OneApp;
@@ -22,17 +31,18 @@ export interface AppContext<TConfig = Record<string, unknown>> {
22
31
  config: TConfig;
23
32
  router?: Router;
24
33
  }
25
- export declare class OneApp<TState extends object = Record<string, unknown>, TConfig extends object = Record<string, unknown>> {
34
+ export declare class OneApp<TState extends object = Record<string, unknown>, TConfig extends object = Record<string, unknown>, TRootProps extends ComponentProps = ComponentProps> {
26
35
  private options;
27
36
  private container;
28
37
  private rootInstance;
29
38
  private mounted;
30
39
  private templateEngine;
31
40
  private readonly appContext;
41
+ private readonly providers;
32
42
  private plugins;
33
43
  private unmountedCallback?;
34
44
  router?: Router;
35
- constructor(options?: AppOptions<TState, TConfig>);
45
+ constructor(options?: AppOptions<TState, TConfig, TRootProps>);
36
46
  private handleError;
37
47
  /**
38
48
  * 渲染错误UI
@@ -57,7 +67,7 @@ export declare class OneApp<TState extends object = Record<string, unknown>, TCo
57
67
  /**
58
68
  * 更新根组件
59
69
  */
60
- updateRootComponent(component: ComponentConstructor): void;
70
+ updateRootComponent(component: ComponentConstructor<TRootProps>): void;
61
71
  /**
62
72
  * 更新应用状态
63
73
  */
@@ -66,6 +76,10 @@ export declare class OneApp<TState extends object = Record<string, unknown>, TCo
66
76
  * 获取应用上下文
67
77
  */
68
78
  getContext(): AppContext<TConfig>;
79
+ provide<T>(key: InjectionKey<T>, value: T): this;
80
+ inject<T>(key: InjectionKey<T>): T | undefined;
81
+ inject<T>(key: InjectionKey<T>, fallback: T): T;
82
+ resolveInjection<T>(key: InjectionKey<T>): InjectionResult<T>;
69
83
  /**
70
84
  * 获取应用状态
71
85
  */
@@ -78,10 +92,18 @@ export declare class OneApp<TState extends object = Record<string, unknown>, TCo
78
92
  * 监听应用卸载
79
93
  */
80
94
  onUnmounted(callback: () => void): this;
95
+ /**
96
+ * 生成应用入口 HTML 文档
97
+ */
98
+ renderHtmlDocument(options?: AppDocumentRenderOptions): string;
81
99
  /**
82
100
  * 解析根元素
83
101
  */
84
102
  private resolveRootElement;
103
+ private resolveMountContainer;
104
+ private createMountDocumentBody;
105
+ private createMountElementFromSelector;
106
+ private mergeDocumentScripts;
85
107
  private onMounted;
86
108
  private onUpdated;
87
109
  private onBeforeUnmount;
@@ -89,4 +111,4 @@ export declare class OneApp<TState extends object = Record<string, unknown>, TCo
89
111
  /**
90
112
  * 创建应用实例
91
113
  */
92
- export declare function createApp<TState extends object = Record<string, unknown>, TConfig extends object = Record<string, unknown>>(options?: AppOptions<TState, TConfig>): OneApp<TState, TConfig>;
114
+ export declare function createApp<TState extends object = Record<string, unknown>, TConfig extends object = Record<string, unknown>, TRootProps extends ComponentProps = ComponentProps>(options?: AppOptions<TState, TConfig, TRootProps>): OneApp<TState, TConfig, TRootProps>;
@@ -4,6 +4,13 @@ import type { VNode } from '../vnode';
4
4
  export type ComponentProps = object;
5
5
  export type ComponentState = object;
6
6
  export type ComponentEventListener = (...args: unknown[]) => void;
7
+ export type InjectionKey<T> = (string | symbol) & {
8
+ readonly __injectionType?: T;
9
+ };
10
+ export interface InjectionResult<T> {
11
+ found: boolean;
12
+ value: T | undefined;
13
+ }
7
14
  export type ComponentConstructor<TProps extends ComponentProps = ComponentProps, TState extends ComponentState = ComponentState> = new (props?: TProps) => Component<TProps, TState>;
8
15
  export type AnyComponentConstructor = new (props?: never) => Component<ComponentProps, ComponentState>;
9
16
  export declare abstract class Component<TProps extends ComponentProps = ComponentProps, TState extends ComponentState = ComponentState> implements ComponentInstance {
@@ -14,8 +21,11 @@ export declare abstract class Component<TProps extends ComponentProps = Componen
14
21
  private readonly templateEngine;
15
22
  private readonly childComponents;
16
23
  private readonly eventListeners;
24
+ private readonly providers;
17
25
  private readonly updateEffect;
18
26
  private appContext;
27
+ private parentComponent;
28
+ private elementChangeListener;
19
29
  protected styleManager: StyleManager;
20
30
  state: TState;
21
31
  mounted: boolean;
@@ -30,6 +40,12 @@ export declare abstract class Component<TProps extends ComponentProps = Componen
30
40
  setProps(props: Partial<TProps>): void;
31
41
  setState(state: Partial<TState>): void;
32
42
  setAppContext(context: unknown): void;
43
+ setParentComponent(parent: ComponentInstance | null): void;
44
+ setElementChangeListener(listener: (previousElement: Node, nextElement: Node) => void): void;
45
+ provide<T>(key: InjectionKey<T>, value: T): void;
46
+ inject<T>(key: InjectionKey<T>): T | undefined;
47
+ inject<T>(key: InjectionKey<T>, fallback: T): T;
48
+ resolveInjection<T>(key: InjectionKey<T>): InjectionResult<T>;
33
49
  getElement(): Node | null;
34
50
  protected beforeMount(): void;
35
51
  protected onMounted(): void;
@@ -40,7 +56,7 @@ export declare abstract class Component<TProps extends ComponentProps = Componen
40
56
  protected getContext(): unknown;
41
57
  protected get router(): unknown;
42
58
  protected emit(eventName: string, ...args: unknown[]): void;
43
- on(eventName: string, listener: ComponentEventListener): void;
59
+ on(eventName: string, listener: ComponentEventListener): () => void;
44
60
  off(eventName: string, listener: ComponentEventListener): void;
45
61
  private createRenderContext;
46
62
  private collectSlots;
@@ -49,5 +65,6 @@ export declare abstract class Component<TProps extends ComponentProps = Componen
49
65
  private trackStateProperties;
50
66
  private getRouterFrom;
51
67
  private getRouterFromGlobalApp;
68
+ private resolveAppInjection;
52
69
  private trackReactiveValue;
53
70
  }
@@ -1,4 +1,4 @@
1
1
  import { Component } from './base';
2
- import type { AnyComponentConstructor, ComponentConstructor, ComponentEventListener, ComponentProps, ComponentState } from './base';
2
+ import type { AnyComponentConstructor, ComponentConstructor, ComponentEventListener, InjectionKey, InjectionResult, ComponentProps, ComponentState } from './base';
3
3
  export { Component };
4
- export type { AnyComponentConstructor, ComponentConstructor, ComponentEventListener, ComponentProps, ComponentState, };
4
+ export type { AnyComponentConstructor, ComponentConstructor, ComponentEventListener, InjectionKey, InjectionResult, ComponentProps, ComponentState, };
@@ -0,0 +1,32 @@
1
+ import type { Renderable } from './renderer/types';
2
+ import { type StyleSheet } from '../style/sheet';
3
+ export { renderStyleSheet, type StyleAtRule, type StyleProperties, type StyleRule, type StyleSheet, type StyleSheetEntry, type StyleValue, } from '../style/sheet';
4
+ export type HtmlAttributeValue = string | number | boolean | null | undefined;
5
+ export type HtmlAttributes = Record<string, HtmlAttributeValue>;
6
+ export type HtmlDocumentBody = Renderable | Renderable[];
7
+ export interface HtmlHeadElement {
8
+ tag: string;
9
+ attributes?: HtmlAttributes;
10
+ text?: string;
11
+ }
12
+ export interface HtmlScript {
13
+ src: string;
14
+ type?: string;
15
+ async?: boolean;
16
+ defer?: boolean;
17
+ attributes?: HtmlAttributes;
18
+ }
19
+ export interface HtmlDocumentOptions {
20
+ title: string;
21
+ body: HtmlDocumentBody;
22
+ lang?: string;
23
+ charset?: string;
24
+ viewport?: string;
25
+ description?: string;
26
+ htmlAttributes?: HtmlAttributes;
27
+ bodyAttributes?: HtmlAttributes;
28
+ head?: HtmlHeadElement[];
29
+ styles?: StyleSheet;
30
+ scripts?: HtmlScript[];
31
+ }
32
+ export declare function renderHtmlDocument(options: HtmlDocumentOptions): string;