@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 CHANGED
@@ -4,14 +4,20 @@
4
4
 
5
5
  ## 特性
6
6
 
7
- - 纯 TypeScript 实现,公开 API 提供类型定义
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.1',
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.1`
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
  ## 贡献
@@ -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>;
@@ -4,3 +4,6 @@ export * from './reactive';
4
4
  export * from './vnode';
5
5
  export * from './template';
6
6
  export * from './renderer';
7
+ export * from './document';
8
+ export * from './model';
9
+ export * from './form';
@@ -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;
@@ -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;
@@ -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 hasKeyedChildren;
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
  }
@@ -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?: string;
21
+ model?: ModelBinding;
21
22
  if?: boolean;
22
23
  show?: boolean;
23
24
  }
24
- export interface HTMLNode {
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=16D14DDEF181BE5A64756E2164756E21
59
- //# sourceMappingURL=index-dgv88dz4.js.map
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
+ }