@geektech/tsone 0.0.1 → 0.0.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/README.md +114 -5
- package/dist/core/app.d.ts +6 -1
- package/dist/core/component/base.d.ts +18 -1
- package/dist/core/component/index.d.ts +2 -2
- package/dist/core/document.d.ts +32 -0
- package/dist/core/form.d.ts +24 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/model.d.ts +18 -0
- package/dist/core/reactive/types.d.ts +5 -0
- package/dist/core/reactive.d.ts +5 -2
- package/dist/core/renderer/types.d.ts +6 -1
- package/dist/core/renderer.d.ts +9 -4
- package/dist/core/vnode.d.ts +20 -16
- package/dist/{index-dgv88dz4.js → index-8wjswsye.js} +6 -2
- package/dist/index-8wjswsye.js.map +10 -0
- package/dist/index-vpx80nq5.js +32 -0
- package/dist/index-vpx80nq5.js.map +10 -0
- package/dist/{index-3j2jsdpc.js → index-ycmc7ga1.js} +413 -93
- package/dist/index-ycmc7ga1.js.map +21 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +214 -7
- package/dist/index.js.map +7 -5
- package/dist/router/index.d.ts +1 -1
- package/dist/router/index.js +6 -4
- package/dist/router/index.js.map +1 -1
- package/dist/style/StyleManager.d.ts +1 -0
- package/dist/style/index.d.ts +1 -0
- package/dist/style/index.js +6 -2
- package/dist/style/index.js.map +1 -1
- package/dist/style/sheet.d.ts +13 -0
- package/package.json +3 -13
- package/dist/index-3j2jsdpc.js.map +0 -20
- package/dist/index-dgv88dz4.js.map +0 -10
package/README.md
CHANGED
|
@@ -4,14 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
## 特性
|
|
6
6
|
|
|
7
|
-
- 纯 TypeScript
|
|
7
|
+
- 纯 TypeScript 实现,TypeScript为第一公民,公开 API 提供类型定义
|
|
8
8
|
- Bun 原生工具链:安装、测试、构建、示例服务和文档服务均由 Bun 驱动
|
|
9
|
+
- 文档内容由 TSone 的 TypeScript typed content registry 提供
|
|
9
10
|
- 响应式系统:`reactive`、`effect`、`computed`
|
|
10
11
|
- 面向对象组件模型:`Component<Props, State>`、生命周期、事件、插槽
|
|
11
12
|
- 策略模式渲染层:文本、元素、组件、插槽按 VNode 类型分发
|
|
12
13
|
- 内置路由:`createRouter`、`RouterView`、`RouterLink`
|
|
13
14
|
- 轻量级运行时,生产包无外部运行时依赖
|
|
14
15
|
|
|
16
|
+
## 仓库结构
|
|
17
|
+
|
|
18
|
+
本仓库是 Bun workspace monorepo,当前发布包位于 `packages/tsone/`。
|
|
19
|
+
根目录命令会代理到该包,包内仍保留自己的源码、测试、示例、文档和发布配置。
|
|
20
|
+
|
|
15
21
|
## 安装
|
|
16
22
|
|
|
17
23
|
```bash
|
|
@@ -42,7 +48,7 @@ class App extends Component<Record<string, never>, AppState> {
|
|
|
42
48
|
protected initState(): AppState {
|
|
43
49
|
return {
|
|
44
50
|
count: 0,
|
|
45
|
-
version: '0.0.
|
|
51
|
+
version: '0.0.2',
|
|
46
52
|
};
|
|
47
53
|
}
|
|
48
54
|
|
|
@@ -128,6 +134,36 @@ bun test
|
|
|
128
134
|
bun run build
|
|
129
135
|
bun run dev
|
|
130
136
|
bun run docs
|
|
137
|
+
bun run docs:build
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
文档站点内容维护在 `packages/tsone/docs/app/content/*.ts` 的 typed content registry 中。
|
|
141
|
+
|
|
142
|
+
生成静态文档产物:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
bun run docs:build
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
基础 HTML 文档壳也可以由 TSone 生成,`body` 传入组件或 VNode,不传 HTML 字符串。该 API 会通过 TSone 渲染器挂载节点;在 Bun/Node 静态生成环境中,请先提供 DOM-like document:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { renderHtmlDocument, type StyleSheet } from '@geektech/tsone';
|
|
152
|
+
|
|
153
|
+
const styles: StyleSheet = [
|
|
154
|
+
{
|
|
155
|
+
selector: '.app',
|
|
156
|
+
properties: { maxWidth: '72rem' },
|
|
157
|
+
},
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
const html = renderHtmlDocument({
|
|
161
|
+
lang: 'zh-CN',
|
|
162
|
+
title: 'TSone App',
|
|
163
|
+
body: { component: App, props: { message: 'Hello TSone' } },
|
|
164
|
+
styles,
|
|
165
|
+
scripts: [{ type: 'module', src: '/assets/app.js' }],
|
|
166
|
+
});
|
|
131
167
|
```
|
|
132
168
|
|
|
133
169
|
## 公开 API
|
|
@@ -137,12 +173,84 @@ bun run docs
|
|
|
137
173
|
- `createApp(options)`
|
|
138
174
|
- `Component<Props, State>`
|
|
139
175
|
- `VNode`
|
|
140
|
-
- `h()` / `createComponent()` / `slot()`
|
|
176
|
+
- `h()` / `createComponent()` / `slot()` / `each()`
|
|
177
|
+
- `Div()` / `Span()` / `P()` / `Button()` / `Input()`
|
|
178
|
+
- `Directions` / `ModelBinding`
|
|
179
|
+
- `InjectionKey`、组件和应用的 `provide()` / `inject()`
|
|
180
|
+
- `createForm()` / `required()` / `minLength()` / `validate()`
|
|
181
|
+
- `renderHtmlDocument(options)`
|
|
182
|
+
- `StyleSheet` / `renderStyleSheet(styles)`
|
|
141
183
|
- `reactive()` / `readonly()`
|
|
142
184
|
- `effect()` / `stop()`
|
|
143
185
|
- `computed()`
|
|
144
186
|
- `ref()` / `isRef()` / `unref()`
|
|
145
|
-
- `version`,当前为 `0.0.
|
|
187
|
+
- `version`,当前为 `0.0.2`
|
|
188
|
+
|
|
189
|
+
## 渲染、通信与表单
|
|
190
|
+
|
|
191
|
+
`directions.if` 可以控制元素、组件或插槽的挂载;不满足条件时会卸载节点:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
{
|
|
195
|
+
component: ProfilePanel,
|
|
196
|
+
directions: { if: this.state.visible },
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`each()` 为列表产生稳定 key,供渲染器在排序、插入和删除时复用节点:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const items = each(
|
|
204
|
+
this.state.users,
|
|
205
|
+
(user) => ({ tag: 'li', children: [user.name] }),
|
|
206
|
+
(user) => user.id
|
|
207
|
+
);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
组件事件可订阅并用返回的函数取消订阅;组件 VNode 可通过 `emitters` 声明父级监听器。
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
const stopListening = child.on('saved', (payload) => console.log(payload));
|
|
214
|
+
stopListening();
|
|
215
|
+
// { component: Editor, emitters: { saved: (payload) => this.save(payload) } }
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
依赖注入从当前组件向父级再到应用实例查找:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
const THEME: InjectionKey<{ mode: string }> = Symbol('theme');
|
|
222
|
+
app.provide(THEME, { mode: 'dark' });
|
|
223
|
+
const theme = this.inject(THEME, { mode: 'light' });
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`directions.model` 支持点分隔路径和转换函数,原生 input、textarea、checkbox、radio 与 select 会同步:
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
Input({
|
|
230
|
+
props: { type: 'number' },
|
|
231
|
+
directions: {
|
|
232
|
+
model: {
|
|
233
|
+
path: 'profile.age',
|
|
234
|
+
parse: (value) => Number(value),
|
|
235
|
+
format: (value) => String(value ?? ''),
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
校验是纯函数,不负责错误 UI 或提交:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
const form = createForm(this.state, {
|
|
245
|
+
'profile.name': [required('请输入姓名'), minLength(2)],
|
|
246
|
+
'profile.age': [
|
|
247
|
+
validate((value) => Number(value) >= 18 || '年龄须不小于 18'),
|
|
248
|
+
],
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const result = form.validate();
|
|
252
|
+
form.resetErrors();
|
|
253
|
+
```
|
|
146
254
|
|
|
147
255
|
路由入口 `@geektech/tsone/router`:
|
|
148
256
|
|
|
@@ -157,6 +265,7 @@ bun run docs
|
|
|
157
265
|
样式入口 `@geektech/tsone/style`:
|
|
158
266
|
|
|
159
267
|
- `StyleManager`
|
|
268
|
+
- `StyleSheet` / `renderStyleSheet(styles)`
|
|
160
269
|
|
|
161
270
|
## 发布前检查
|
|
162
271
|
|
|
@@ -164,7 +273,7 @@ bun run docs
|
|
|
164
273
|
bun test
|
|
165
274
|
bunx tsc --noEmit
|
|
166
275
|
bun run build
|
|
167
|
-
bun pm pack --dry-run
|
|
276
|
+
bun pm pack --cwd packages/tsone --dry-run
|
|
168
277
|
```
|
|
169
278
|
|
|
170
279
|
## 贡献
|
package/dist/core/app.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ComponentConstructor } from './component';
|
|
1
|
+
import { ComponentConstructor, InjectionKey, InjectionResult } from './component';
|
|
2
2
|
import type { Router } from '../router';
|
|
3
3
|
export interface Plugin {
|
|
4
4
|
install: (app: OneApp, ...args: unknown[]) => void;
|
|
@@ -29,6 +29,7 @@ export declare class OneApp<TState extends object = Record<string, unknown>, TCo
|
|
|
29
29
|
private mounted;
|
|
30
30
|
private templateEngine;
|
|
31
31
|
private readonly appContext;
|
|
32
|
+
private readonly providers;
|
|
32
33
|
private plugins;
|
|
33
34
|
private unmountedCallback?;
|
|
34
35
|
router?: Router;
|
|
@@ -66,6 +67,10 @@ export declare class OneApp<TState extends object = Record<string, unknown>, TCo
|
|
|
66
67
|
* 获取应用上下文
|
|
67
68
|
*/
|
|
68
69
|
getContext(): AppContext<TConfig>;
|
|
70
|
+
provide<T>(key: InjectionKey<T>, value: T): this;
|
|
71
|
+
inject<T>(key: InjectionKey<T>): T | undefined;
|
|
72
|
+
inject<T>(key: InjectionKey<T>, fallback: T): T;
|
|
73
|
+
resolveInjection<T>(key: InjectionKey<T>): InjectionResult<T>;
|
|
69
74
|
/**
|
|
70
75
|
* 获取应用状态
|
|
71
76
|
*/
|
|
@@ -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;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type ValidationResult = boolean | string;
|
|
2
|
+
export type ValidationRule<T = unknown> = (value: T, model: object) => ValidationResult;
|
|
3
|
+
export type ValidationRules<TModel extends object> = {
|
|
4
|
+
[TPath in keyof TModel & string]?: readonly ValidationRule[];
|
|
5
|
+
} & Record<string, readonly ValidationRule[] | undefined>;
|
|
6
|
+
export interface FieldValidationResult {
|
|
7
|
+
valid: boolean;
|
|
8
|
+
errors: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface FormValidationResult {
|
|
11
|
+
valid: boolean;
|
|
12
|
+
errors: Record<string, string[]>;
|
|
13
|
+
}
|
|
14
|
+
export interface FormController<TModel extends object> {
|
|
15
|
+
readonly model: TModel;
|
|
16
|
+
readonly errors: Record<string, string[]>;
|
|
17
|
+
validate(): FormValidationResult;
|
|
18
|
+
validateField(path: string): FieldValidationResult;
|
|
19
|
+
resetErrors(): void;
|
|
20
|
+
}
|
|
21
|
+
export declare function required(message?: string): ValidationRule;
|
|
22
|
+
export declare function minLength(length: number, message?: string): ValidationRule;
|
|
23
|
+
export declare function validate(rule: ValidationRule): ValidationRule;
|
|
24
|
+
export declare function createForm<TModel extends object>(model: TModel, rules: ValidationRules<TModel>): FormController<TModel>;
|
package/dist/core/index.d.ts
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ModelBindingOptions {
|
|
2
|
+
path: string;
|
|
3
|
+
parse?: (value: unknown) => unknown;
|
|
4
|
+
format?: (value: unknown) => string;
|
|
5
|
+
}
|
|
6
|
+
export type ModelBinding = string | ModelBindingOptions;
|
|
7
|
+
type ModelState = Record<string, unknown>;
|
|
8
|
+
export declare function modelPath(binding: ModelBinding): string;
|
|
9
|
+
export declare function getModelValue(state: ModelState, path: string): unknown;
|
|
10
|
+
export declare function setModelValue(state: ModelState, path: string, value: unknown): void;
|
|
11
|
+
export declare class ModelBindingController {
|
|
12
|
+
private readonly bindings;
|
|
13
|
+
bind(element: HTMLElement, binding: ModelBinding, state: ModelState): void;
|
|
14
|
+
cleanup(element: HTMLElement): void;
|
|
15
|
+
private isSupportedControl;
|
|
16
|
+
private sameBinding;
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
@@ -8,12 +8,17 @@ export interface ReactiveEffect<T = unknown> {
|
|
|
8
8
|
export interface ReactiveEffectOptions {
|
|
9
9
|
lazy?: boolean;
|
|
10
10
|
scheduler?: (effect: ReactiveEffect) => void;
|
|
11
|
+
throwOnError?: boolean;
|
|
11
12
|
}
|
|
12
13
|
export interface ComputedRef<T> {
|
|
13
14
|
readonly value: T;
|
|
14
15
|
}
|
|
15
16
|
export declare const IS_REACTIVE: unique symbol;
|
|
16
17
|
export declare const IS_READONLY: unique symbol;
|
|
18
|
+
export declare const IS_REF: unique symbol;
|
|
19
|
+
export interface Ref<T = unknown> {
|
|
20
|
+
value: T;
|
|
21
|
+
}
|
|
17
22
|
export declare const MUTATING_ARRAY_METHODS: readonly ["push", "pop", "shift", "unshift", "splice", "sort", "reverse"];
|
|
18
23
|
export declare function hasReactiveFlag(value: object, flag: typeof IS_REACTIVE | typeof IS_READONLY): boolean;
|
|
19
24
|
export declare function isObject(value: unknown): value is object;
|
package/dist/core/reactive.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ComputedRef, ReactiveEffect, ReactiveEffectOptions } from './reactive/types';
|
|
2
|
-
export type { ComputedRef, ReactiveEffect, ReactiveEffectOptions, } from './reactive/types';
|
|
1
|
+
import { ComputedRef, Ref, ReactiveEffect, ReactiveEffectOptions } from './reactive/types';
|
|
2
|
+
export type { ComputedRef, Ref, ReactiveEffect, ReactiveEffectOptions, } from './reactive/types';
|
|
3
3
|
export declare class ReactiveSystem {
|
|
4
4
|
private static instance;
|
|
5
5
|
private activeEffect;
|
|
@@ -26,6 +26,9 @@ export declare function reactive<T extends object>(target: T): T;
|
|
|
26
26
|
export declare function readonly<T extends object>(target: T): Readonly<T>;
|
|
27
27
|
export declare function effect<T = unknown>(fn: () => T, options?: ReactiveEffectOptions): ReactiveEffect<T>;
|
|
28
28
|
export declare function computed<T>(getter: () => T): ComputedRef<T>;
|
|
29
|
+
export declare function ref<T>(value: T): Ref<T>;
|
|
30
|
+
export declare function isRef(value: unknown): value is Ref<unknown>;
|
|
31
|
+
export declare function unref<T>(value: T | Ref<T>): T;
|
|
29
32
|
export declare function stop(effect: ReactiveEffect): void;
|
|
30
33
|
export declare function isReactive(value: unknown): boolean;
|
|
31
34
|
export declare function isReadonly(value: unknown): boolean;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TemplateEngine } from '../template';
|
|
2
2
|
import type { ComponentEventListener, ComponentProps } from '../component';
|
|
3
|
+
import type { InjectionKey, InjectionResult } from '../component';
|
|
3
4
|
import type { VNode } from '../vnode';
|
|
4
5
|
export interface ComponentInstance {
|
|
5
6
|
mountToNode(): Node;
|
|
@@ -7,8 +8,11 @@ export interface ComponentInstance {
|
|
|
7
8
|
unmount(): void;
|
|
8
9
|
setProps(props: Partial<ComponentProps>): void;
|
|
9
10
|
setAppContext?(context: unknown): void;
|
|
11
|
+
setParentComponent?(parent: ComponentInstance | null): void;
|
|
12
|
+
setElementChangeListener?(listener: (previousElement: Node, nextElement: Node) => void): void;
|
|
13
|
+
resolveInjection?<T>(key: InjectionKey<T>): InjectionResult<T>;
|
|
10
14
|
getElement(): Node | null;
|
|
11
|
-
on(eventName: string, listener: ComponentEventListener): void;
|
|
15
|
+
on(eventName: string, listener: ComponentEventListener): () => void;
|
|
12
16
|
}
|
|
13
17
|
export type Renderable = VNode | string;
|
|
14
18
|
export interface RendererHost {
|
|
@@ -22,6 +26,7 @@ export interface RenderRuntimeContext {
|
|
|
22
26
|
renderer: RendererHost;
|
|
23
27
|
slots: Record<string, Array<VNode | string>>;
|
|
24
28
|
registerChild(component: ComponentInstance): void;
|
|
29
|
+
unregisterChild(component: ComponentInstance): void;
|
|
25
30
|
}
|
|
26
31
|
export interface RenderStrategy<T extends Renderable = Renderable> {
|
|
27
32
|
matches(vnode: Renderable): vnode is T;
|
package/dist/core/renderer.d.ts
CHANGED
|
@@ -17,11 +17,17 @@ export declare class TextRenderStrategy implements RenderStrategy<string> {
|
|
|
17
17
|
}
|
|
18
18
|
export declare class ComponentRenderStrategy implements RenderStrategy<ComponentNode> {
|
|
19
19
|
private readonly instances;
|
|
20
|
+
private readonly instanceNodes;
|
|
21
|
+
private readonly emitterUnsubscribers;
|
|
20
22
|
matches(vnode: Renderable): vnode is ComponentNode;
|
|
21
23
|
mount(vnode: ComponentNode, context: RenderRuntimeContext): Node;
|
|
22
24
|
patch(oldVNode: ComponentNode, newVNode: ComponentNode, currentNode: Node, context: RenderRuntimeContext): Node;
|
|
23
|
-
unmount(_vnode: ComponentNode, currentNode: Node): void;
|
|
25
|
+
unmount(_vnode: ComponentNode, currentNode: Node, context: RenderRuntimeContext): void;
|
|
24
26
|
private createProps;
|
|
27
|
+
private syncEmitters;
|
|
28
|
+
private clearEmitters;
|
|
29
|
+
private trackInstanceNode;
|
|
30
|
+
private clearInstanceNodes;
|
|
25
31
|
}
|
|
26
32
|
export declare class SlotRenderStrategy implements RenderStrategy<SlotProvider> {
|
|
27
33
|
private readonly renderedChildren;
|
|
@@ -46,12 +52,11 @@ export declare class ElementRenderStrategy implements RenderStrategy<HTMLNode> {
|
|
|
46
52
|
private applyDirections;
|
|
47
53
|
private updateChildren;
|
|
48
54
|
private updateKeyedChildren;
|
|
49
|
-
private
|
|
55
|
+
private hasOnlyKeyedChildren;
|
|
56
|
+
private assertNoDuplicateKeys;
|
|
50
57
|
private getVNodeKey;
|
|
51
58
|
private collectListeners;
|
|
52
59
|
private updateListeners;
|
|
53
60
|
private setupReactiveAttribute;
|
|
54
|
-
private setupTwoWayBinding;
|
|
55
|
-
private getStateValue;
|
|
56
61
|
private trackEffect;
|
|
57
62
|
}
|
package/dist/core/vnode.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { AnyComponentConstructor, ComponentConstructor, ComponentEventListener, ComponentProps } from './component';
|
|
2
|
+
import type { ModelBinding } from './model';
|
|
2
3
|
export interface ComponentType {
|
|
3
4
|
mount(container: HTMLElement): void;
|
|
4
5
|
unmount(): void;
|
|
5
|
-
on(eventName: string, listener: ComponentEventListener): void;
|
|
6
|
+
on(eventName: string, listener: ComponentEventListener): () => void;
|
|
6
7
|
}
|
|
7
8
|
type VNodeComponentProps = ComponentProps;
|
|
8
9
|
export type VNodeComponentConstructor<P extends VNodeComponentProps = VNodeComponentProps> = ComponentConstructor<P> | AnyComponentConstructor;
|
|
@@ -17,48 +18,51 @@ export interface EventListeners {
|
|
|
17
18
|
[eventName: string]: (event: Event) => void;
|
|
18
19
|
}
|
|
19
20
|
export interface Directions {
|
|
20
|
-
model?:
|
|
21
|
+
model?: ModelBinding;
|
|
21
22
|
if?: boolean;
|
|
22
23
|
show?: boolean;
|
|
23
24
|
}
|
|
24
|
-
export interface
|
|
25
|
+
export interface VNodeBase {
|
|
26
|
+
key?: string | number;
|
|
27
|
+
slot?: string;
|
|
28
|
+
directions?: Directions;
|
|
29
|
+
}
|
|
30
|
+
export interface HTMLNode extends VNodeBase {
|
|
25
31
|
tag: string;
|
|
26
32
|
props?: HTMLProps;
|
|
27
33
|
children?: Array<VNode | string>;
|
|
28
34
|
listeners?: EventListeners;
|
|
29
|
-
key?: string | number;
|
|
30
|
-
slot?: string;
|
|
31
|
-
directions?: Directions;
|
|
32
35
|
}
|
|
33
|
-
export interface ComponentNode<P extends VNodeComponentProps = VNodeComponentProps> {
|
|
36
|
+
export interface ComponentNode<P extends VNodeComponentProps = VNodeComponentProps> extends VNodeBase {
|
|
34
37
|
component: VNodeComponentConstructor<P>;
|
|
35
38
|
props?: P;
|
|
36
39
|
children?: Array<VNode | string>;
|
|
37
40
|
emitters?: Record<string, ComponentEventListener>;
|
|
38
|
-
key?: string | number;
|
|
39
|
-
slot?: string;
|
|
40
|
-
directions?: Directions;
|
|
41
41
|
}
|
|
42
|
-
export interface SlotProvider {
|
|
42
|
+
export interface SlotProvider extends VNodeBase {
|
|
43
43
|
tag: 'slot';
|
|
44
44
|
props: {
|
|
45
45
|
name: string;
|
|
46
46
|
};
|
|
47
47
|
children?: Array<VNode | string>;
|
|
48
|
-
key?: string | number;
|
|
49
|
-
directions?: Directions;
|
|
50
48
|
}
|
|
51
|
-
export interface SlotInjector {
|
|
49
|
+
export interface SlotInjector extends VNodeBase {
|
|
52
50
|
tag: string;
|
|
53
51
|
slot: string;
|
|
54
|
-
key?: string | number;
|
|
55
|
-
directions?: Directions;
|
|
56
52
|
}
|
|
57
53
|
export type VNode = HTMLNode | ComponentNode | SlotProvider | SlotInjector;
|
|
54
|
+
export type ElementShortcutOptions = Omit<HTMLNode, 'tag'>;
|
|
55
|
+
export type ElementShortcut = (options?: ElementShortcutOptions) => HTMLNode;
|
|
58
56
|
export declare function isComponentNode(vnode: VNode): vnode is ComponentNode;
|
|
59
57
|
export declare function isHTMLNode(vnode: VNode): vnode is HTMLNode;
|
|
60
58
|
export declare function isSlotProvider(vnode: VNode): vnode is SlotProvider;
|
|
61
59
|
export declare function h(tag: string, props?: HTMLProps, children?: Array<VNode | string>, listeners?: EventListeners, key?: string | number, directions?: Directions): HTMLNode;
|
|
60
|
+
export declare const Div: ElementShortcut;
|
|
61
|
+
export declare const Span: ElementShortcut;
|
|
62
|
+
export declare const P: ElementShortcut;
|
|
63
|
+
export declare const Button: ElementShortcut;
|
|
64
|
+
export declare const Input: ElementShortcut;
|
|
62
65
|
export declare function createComponent<P extends VNodeComponentProps>(componentClass: ComponentConstructor<P>, props?: P, children?: Array<VNode | string>, key?: string | number, directions?: Directions): ComponentNode<P>;
|
|
63
66
|
export declare function slot(name: string, key?: string | number, directions?: Directions): SlotProvider;
|
|
67
|
+
export declare function each<T>(items: readonly T[], render: (item: T, index: number) => VNode | string, key: (item: T, index: number) => string | number): VNode[];
|
|
64
68
|
export {};
|
|
@@ -18,6 +18,10 @@ class StyleManager {
|
|
|
18
18
|
this.styles.clear();
|
|
19
19
|
this.styleElement.textContent = "";
|
|
20
20
|
}
|
|
21
|
+
destroy() {
|
|
22
|
+
this.clearStyles();
|
|
23
|
+
this.styleElement.remove();
|
|
24
|
+
}
|
|
21
25
|
convertToCSS(properties) {
|
|
22
26
|
return Object.entries(properties).map(([key, value]) => {
|
|
23
27
|
const cssKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
@@ -55,5 +59,5 @@ ${this.convertToCSS(properties)}
|
|
|
55
59
|
|
|
56
60
|
export { StyleManager };
|
|
57
61
|
|
|
58
|
-
//# debugId=
|
|
59
|
-
//# sourceMappingURL=index-
|
|
62
|
+
//# debugId=401A1B62728D384364756E2164756E21
|
|
63
|
+
//# sourceMappingURL=index-8wjswsye.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../lib/style/StyleManager.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"export interface StyleOptions {\n selector: string;\n properties: Record<string, string | number>;\n hover?: Record<string, string | number>;\n media?: Record<string, Record<string, string | number>>;\n}\n\nexport class StyleManager {\n public styleElement: HTMLStyleElement;\n public styles: Map<string, StyleOptions> = new Map();\n\n constructor() {\n this.styleElement = document.createElement('style');\n document.head.appendChild(this.styleElement);\n }\n\n public addStyle(name: string, options: StyleOptions): void {\n this.styles.set(name, options);\n this.updateStyles();\n }\n\n public removeStyle(name: string): void {\n this.styles.delete(name);\n this.updateStyles();\n }\n\n public clearStyles(): void {\n this.styles.clear();\n this.styleElement.textContent = '';\n }\n\n public destroy(): void {\n this.clearStyles();\n this.styleElement.remove();\n }\n\n private convertToCSS(properties: Record<string, string | number>): string {\n return Object.entries(properties)\n .map(([key, value]) => {\n const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();\n return `${cssKey}: ${value};`;\n })\n .join('\\n');\n }\n\n private updateStyles(): void {\n let cssText = '';\n\n this.styles.forEach((style) => {\n // 基础样式\n cssText += `${style.selector} {\\n${this.convertToCSS(style.properties)}\\n}\\n`;\n\n // hover 样式\n if (style.hover) {\n cssText += `${style.selector}:hover {\\n${this.convertToCSS(style.hover)}\\n}\\n`;\n }\n\n // media 查询\n if (style.media) {\n Object.entries(style.media).forEach(([query, properties]) => {\n cssText += `@media ${query} {\\n${style.selector} {\\n${this.convertToCSS(properties)}\\n}\\n}\\n`;\n });\n }\n });\n\n this.styleElement.textContent = cssText;\n }\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";AAOO,MAAM,aAAa;AAAA,EACjB;AAAA,EACA,SAAoC,IAAI;AAAA,EAE/C,WAAW,GAAG;AAAA,IACZ,KAAK,eAAe,SAAS,cAAc,OAAO;AAAA,IAClD,SAAS,KAAK,YAAY,KAAK,YAAY;AAAA;AAAA,EAGtC,QAAQ,CAAC,MAAc,SAA6B;AAAA,IACzD,KAAK,OAAO,IAAI,MAAM,OAAO;AAAA,IAC7B,KAAK,aAAa;AAAA;AAAA,EAGb,WAAW,CAAC,MAAoB;AAAA,IACrC,KAAK,OAAO,OAAO,IAAI;AAAA,IACvB,KAAK,aAAa;AAAA;AAAA,EAGb,WAAW,GAAS;AAAA,IACzB,KAAK,OAAO,MAAM;AAAA,IAClB,KAAK,aAAa,cAAc;AAAA;AAAA,EAG3B,OAAO,GAAS;AAAA,IACrB,KAAK,YAAY;AAAA,IACjB,KAAK,aAAa,OAAO;AAAA;AAAA,EAGnB,YAAY,CAAC,YAAqD;AAAA,IACxE,OAAO,OAAO,QAAQ,UAAU,EAC7B,IAAI,EAAE,KAAK,WAAW;AAAA,MACrB,MAAM,SAAS,IAAI,QAAQ,YAAY,KAAK,EAAE,YAAY;AAAA,MAC1D,OAAO,GAAG,WAAW;AAAA,KACtB,EACA,KAAK;AAAA,CAAI;AAAA;AAAA,EAGN,YAAY,GAAS;AAAA,IAC3B,IAAI,UAAU;AAAA,IAEd,KAAK,OAAO,QAAQ,CAAC,UAAU;AAAA,MAE7B,WAAW,GAAG,MAAM;AAAA,EAAe,KAAK,aAAa,MAAM,UAAU;AAAA;AAAA;AAAA,MAGrE,IAAI,MAAM,OAAO;AAAA,QACf,WAAW,GAAG,MAAM;AAAA,EAAqB,KAAK,aAAa,MAAM,KAAK;AAAA;AAAA;AAAA,MACxE;AAAA,MAGA,IAAI,MAAM,OAAO;AAAA,QACf,OAAO,QAAQ,MAAM,KAAK,EAAE,QAAQ,EAAE,OAAO,gBAAgB;AAAA,UAC3D,WAAW,UAAU;AAAA,EAAY,MAAM;AAAA,EAAe,KAAK,aAAa,UAAU;AAAA;AAAA;AAAA;AAAA,SACnF;AAAA,MACH;AAAA,KACD;AAAA,IAED,KAAK,aAAa,cAAc;AAAA;AAEpC;",
|
|
8
|
+
"debugId": "401A1B62728D384364756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// lib/style/sheet.ts
|
|
2
|
+
function renderStyleSheet(styles) {
|
|
3
|
+
return styles.map((entry) => renderStyleEntry(entry)).join(`
|
|
4
|
+
|
|
5
|
+
`);
|
|
6
|
+
}
|
|
7
|
+
function renderStyleEntry(entry, indent = "") {
|
|
8
|
+
if ("atRule" in entry) {
|
|
9
|
+
const rules = entry.rules.map((rule) => renderStyleEntry(rule, `${indent} `)).join(`
|
|
10
|
+
|
|
11
|
+
`);
|
|
12
|
+
return `${indent}${entry.atRule} {
|
|
13
|
+
${rules}
|
|
14
|
+
${indent}}`;
|
|
15
|
+
}
|
|
16
|
+
const declarations = Object.entries(entry.properties).map(([property, value]) => `${indent} ${toCssProperty(property)}: ${String(value)};`).join(`
|
|
17
|
+
`);
|
|
18
|
+
return `${indent}${entry.selector} {
|
|
19
|
+
${declarations}
|
|
20
|
+
${indent}}`;
|
|
21
|
+
}
|
|
22
|
+
function toCssProperty(property) {
|
|
23
|
+
if (property.startsWith("--") || property.includes("-")) {
|
|
24
|
+
return property;
|
|
25
|
+
}
|
|
26
|
+
return property.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { renderStyleSheet };
|
|
30
|
+
|
|
31
|
+
//# debugId=61B1187B7ADC17DB64756E2164756E21
|
|
32
|
+
//# sourceMappingURL=index-vpx80nq5.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../lib/style/sheet.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"export type StyleValue = string | number;\n\nexport type StyleProperties = Record<string, StyleValue>;\n\nexport interface StyleRule {\n selector: string;\n properties: StyleProperties;\n}\n\nexport interface StyleAtRule {\n atRule: string;\n rules: StyleRule[];\n}\n\nexport type StyleSheetEntry = StyleRule | StyleAtRule;\n\nexport type StyleSheet = StyleSheetEntry[];\n\nexport function renderStyleSheet(styles: StyleSheet): string {\n return styles.map((entry) => renderStyleEntry(entry)).join('\\n\\n');\n}\n\nfunction renderStyleEntry(entry: StyleSheetEntry, indent = ''): string {\n if ('atRule' in entry) {\n const rules = entry.rules\n .map((rule) => renderStyleEntry(rule, `${indent} `))\n .join('\\n\\n');\n\n return `${indent}${entry.atRule} {\\n${rules}\\n${indent}}`;\n }\n\n const declarations = Object.entries(entry.properties)\n .map(\n ([property, value]) =>\n `${indent} ${toCssProperty(property)}: ${String(value)};`\n )\n .join('\\n');\n\n return `${indent}${entry.selector} {\\n${declarations}\\n${indent}}`;\n}\n\nfunction toCssProperty(property: string): string {\n if (property.startsWith('--') || property.includes('-')) {\n return property;\n }\n\n return property.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";AAkBO,SAAS,gBAAgB,CAAC,QAA4B;AAAA,EAC3D,OAAO,OAAO,IAAI,CAAC,UAAU,iBAAiB,KAAK,CAAC,EAAE,KAAK;AAAA;AAAA,CAAM;AAAA;AAGnE,SAAS,gBAAgB,CAAC,OAAwB,SAAS,IAAY;AAAA,EACrE,IAAI,YAAY,OAAO;AAAA,IACrB,MAAM,QAAQ,MAAM,MACjB,IAAI,CAAC,SAAS,iBAAiB,MAAM,GAAG,UAAU,CAAC,EACnD,KAAK;AAAA;AAAA,CAAM;AAAA,IAEd,OAAO,GAAG,SAAS,MAAM;AAAA,EAAa;AAAA,EAAU;AAAA,EAClD;AAAA,EAEA,MAAM,eAAe,OAAO,QAAQ,MAAM,UAAU,EACjD,IACC,EAAE,UAAU,WACV,GAAG,WAAW,cAAc,QAAQ,MAAM,OAAO,KAAK,IAC1D,EACC,KAAK;AAAA,CAAI;AAAA,EAEZ,OAAO,GAAG,SAAS,MAAM;AAAA,EAAe;AAAA,EAAiB;AAAA;AAG3D,SAAS,aAAa,CAAC,UAA0B;AAAA,EAC/C,IAAI,SAAS,WAAW,IAAI,KAAK,SAAS,SAAS,GAAG,GAAG;AAAA,IACvD,OAAO;AAAA,EACT;AAAA,EAEA,OAAO,SAAS,QAAQ,UAAU,CAAC,UAAU,IAAI,MAAM,YAAY,GAAG;AAAA;",
|
|
8
|
+
"debugId": "61B1187B7ADC17DB64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|