vanilla-jui 1.4.8 → 1.5.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.
Files changed (58) hide show
  1. package/README.md +10 -4
  2. package/dist/index.d.ts +402 -977
  3. package/dist/index.js +1 -1
  4. package/dist/index.umd.js +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +3 -3
  7. package/src/components/accordion.ts +467 -349
  8. package/src/components/flow.ts +779 -1133
  9. package/src/components/form.ts +666 -509
  10. package/src/components/menu.ts +199 -326
  11. package/src/components/modal.ts +510 -747
  12. package/src/components/offcanvas.ts +281 -361
  13. package/src/components/pagination.ts +207 -374
  14. package/src/components/swiper.ts +688 -1168
  15. package/src/components/tabs.ts +468 -788
  16. package/src/components/toc.ts +191 -244
  17. package/src/core/component.ts +332 -0
  18. package/src/core/motion.ts +298 -0
  19. package/src/core/presence.ts +233 -0
  20. package/src/core/scheduler.ts +91 -0
  21. package/src/core/view.ts +31 -0
  22. package/src/css/accordion.css +94 -3
  23. package/src/css/color.css +4 -5
  24. package/src/css/color_p3.css +6 -5
  25. package/src/css/content.css +18 -24
  26. package/src/css/drop.css +4 -1
  27. package/src/css/form.css +8 -0
  28. package/src/css/gradient.css +64 -131
  29. package/src/css/layout.css +9 -23
  30. package/src/css/menu.css +10 -5
  31. package/src/css/modal.css +2 -1
  32. package/src/css/offcanvas.css +1 -17
  33. package/src/css/swiper.css +2 -1
  34. package/src/css/tabs.css +8 -17
  35. package/src/css/themes/default/accordion.css +0 -48
  36. package/src/css/themes/default/swiper.css +1 -1
  37. package/src/css/themes/default/tabs.css +4 -6
  38. package/src/css/toast.css +1 -17
  39. package/src/css/tokens.css +18 -35
  40. package/src/css/tooltip.css +2 -1
  41. package/src/index.ts +8 -3
  42. package/src/locales/index.ts +3 -0
  43. package/src/primitives/drop.ts +253 -262
  44. package/src/primitives/loading.ts +22 -4
  45. package/src/primitives/parabola.ts +123 -164
  46. package/src/primitives/sticky.ts +297 -0
  47. package/src/primitives/theme.ts +164 -222
  48. package/src/primitives/toast.ts +309 -284
  49. package/src/primitives/tooltip.ts +65 -66
  50. package/src/utilities/browser.ts +38 -0
  51. package/src/utilities/dom.ts +0 -54
  52. package/src/utilities/refs.ts +61 -0
  53. package/src/utilities/state.ts +73 -0
  54. package/src/utilities/timer.ts +11 -31
  55. package/src/validation/validator.ts +289 -389
  56. package/src/components/sticky.ts +0 -297
  57. package/src/core/Component.ts +0 -377
  58. package/src/utilities/scheduler.ts +0 -156
@@ -1,297 +0,0 @@
1
- import { createDeepStore } from 'vanilla-signal';
2
-
3
- import Component, {
4
- type ComponentDOM,
5
- type ComponentRuntime,
6
- } from '../core/Component.ts';
7
- import { type DOMReference, all, requireContainer } from '../utilities/dom.ts';
8
- import { type ResolveSchema, resolveProps } from '../utilities/types.ts';
9
-
10
- type StickyOverflow = 'destroy' | 'ignore';
11
-
12
- interface StickyProps extends Record<string, unknown> {
13
- target?: DOMReference;
14
- parent?: DOMReference;
15
- max?: number;
16
- top?: number;
17
- gap?: number;
18
- overflow?: StickyOverflow;
19
- onRefresh?: ((sticky: StickyInstance) => void) | null;
20
- }
21
-
22
- interface ResolvedStickyProps extends Record<string, unknown> {
23
- target: DOMReference;
24
- parent: DOMReference;
25
- max: number;
26
- top: number;
27
- gap: number;
28
- overflow: StickyOverflow;
29
- onRefresh: ((sticky: StickyInstance) => void) | null;
30
- }
31
-
32
- interface StickyStateItem {
33
- element: HTMLElement;
34
- top: number;
35
- }
36
-
37
- interface StickyState extends Record<string, unknown> {
38
- items: StickyStateItem[];
39
- }
40
-
41
- interface StickyDOM extends ComponentDOM {
42
- root: Element | null;
43
- parent: Element | null;
44
- targets: HTMLElement[];
45
- }
46
-
47
- interface StickyOriginalStyle {
48
- element: HTMLElement;
49
- originalPosition: string;
50
- originalTop: string;
51
- originalZIndex: string;
52
- }
53
-
54
- interface StickyRuntime extends ComponentRuntime {
55
- built: boolean;
56
- }
57
-
58
- interface StickyCache {
59
- originalStyles: StickyOriginalStyle[];
60
- }
61
-
62
- type StickyInstance = Component<
63
- ResolvedStickyProps,
64
- StickyState,
65
- StickyDOM,
66
- StickyCache
67
- > & {
68
- runtime: StickyRuntime;
69
- build(): StickyInstance;
70
- refresh(): StickyInstance;
71
- };
72
-
73
- const STICKY_PROPS_SCHEMA = {
74
- target: { default: null },
75
- parent: { default: null },
76
- max: {
77
- default: 10,
78
- type: 'number',
79
- integer: true,
80
- greaterThan: 0,
81
- },
82
- top: {
83
- default: 16,
84
- type: 'number',
85
- min: 0,
86
- },
87
- gap: {
88
- default: 16,
89
- type: 'number',
90
- min: 0,
91
- },
92
- overflow: {
93
- default: 'destroy',
94
- type: 'string',
95
- enum: ['destroy', 'ignore'],
96
- },
97
- onRefresh: { default: null, types: ['function', 'null'] },
98
- } satisfies ResolveSchema<StickyProps>;
99
-
100
- function normalizeProps(input: StickyProps): ResolvedStickyProps {
101
- const props = resolveProps(input, STICKY_PROPS_SCHEMA, 'Sticky.props');
102
- return {
103
- target: props.target as DOMReference,
104
- parent: props.parent as DOMReference,
105
- max: props.max as number,
106
- top: props.top as number,
107
- gap: props.gap as number,
108
- overflow: props.overflow as StickyOverflow,
109
- onRefresh: props.onRefresh as ResolvedStickyProps['onRefresh'],
110
- };
111
- }
112
-
113
- function uniqueElements(elements: readonly Node[]): HTMLElement[] {
114
- const htmlElements = elements.filter(
115
- (element): element is HTMLElement => element instanceof HTMLElement
116
- );
117
- return Array.from(new Set(htmlElements));
118
- }
119
-
120
- function resolveParent(parent: DOMReference): Element | null {
121
- if (parent === false || parent == null) return null;
122
- return requireContainer(parent, 'Sticky.parent', 'element');
123
- }
124
-
125
- function resolveTarget(
126
- target: DOMReference,
127
- parent: Element | null
128
- ): HTMLElement[] {
129
- if (target === false || target == null) return [];
130
-
131
- if (typeof target === 'string') {
132
- if (!parent) {
133
- return uniqueElements(requireContainer(target, 'Sticky.target', 'array'));
134
- }
135
-
136
- const elements = uniqueElements(all<HTMLElement>(target, parent));
137
- if (elements.length === 0) {
138
- throw new Error('Sticky.target: target not found.');
139
- }
140
- return elements;
141
- }
142
-
143
- const elements = uniqueElements(
144
- requireContainer(target, 'Sticky.target', 'array')
145
- );
146
- if (!parent) return elements;
147
-
148
- const scopedElements = elements.filter(
149
- (element) => element === parent || parent.contains(element)
150
- );
151
- if (scopedElements.length === 0) {
152
- throw new Error('Sticky.target: target not found in parent.');
153
- }
154
- return scopedElements;
155
- }
156
-
157
- /**
158
- * Sticky 吸附组件。
159
- *
160
- * 用于给一个或多个元素应用 `position: sticky`,并按顺序计算 `top`
161
- * 偏移,适合页面侧边栏中多个 widget 的堆叠吸附场景。
162
- */
163
- class Sticky extends Component<
164
- ResolvedStickyProps,
165
- StickyState,
166
- StickyDOM,
167
- StickyCache
168
- > {
169
- declare runtime: StickyRuntime;
170
- declare cache: StickyCache;
171
-
172
- /**
173
- * 创建 Sticky 实例。
174
- * @param {object} [props={}] Sticky 配置。
175
- */
176
- constructor(props: StickyProps = {}) {
177
- const settings = normalizeProps(props);
178
- super(settings);
179
-
180
- this.dom.parent = null;
181
- this.dom.targets = [];
182
- this.cache.originalStyles = [];
183
-
184
- this.runtime.built = false;
185
-
186
- this.state = createDeepStore({
187
- items: [],
188
- });
189
- }
190
-
191
- /**
192
- * 构建 Sticky 行为并应用样式。
193
- * @returns {Sticky} 当前实例。
194
- */
195
- build(): this {
196
- if (this.runtime.destroyed)
197
- throw new Error('Sticky.build: instance destroyed');
198
- if (this.runtime.built) return this;
199
-
200
- this.init(this.props);
201
-
202
- this.dom.parent = resolveParent(this.props.parent);
203
- this.dom.targets = resolveTarget(this.props.target, this.dom.parent);
204
- this.dom.targets = this.resolveOverflow(this.dom.targets);
205
-
206
- this.runtime.built = true;
207
-
208
- if (this.dom.targets.length === 0) return this;
209
-
210
- this.captureOriginalStyles();
211
- this.apply();
212
- return this;
213
- }
214
-
215
- private captureOriginalStyles(): void {
216
- this.cache.originalStyles = this.dom.targets.map((element) => ({
217
- element,
218
- originalPosition: element.style.position,
219
- originalTop: element.style.top,
220
- originalZIndex: element.style.zIndex,
221
- }));
222
- }
223
-
224
- private resolveOverflow(targets: HTMLElement[]): HTMLElement[] {
225
- const { max, overflow } = this.props;
226
- if (targets.length <= max) return targets;
227
-
228
- return overflow === 'ignore' ? [] : targets.slice(-max);
229
- }
230
-
231
- private apply(startTop = this.props.top): number {
232
- let nextTop = startTop;
233
- const stateItems: StickyStateItem[] = [];
234
-
235
- for (const element of this.dom.targets) {
236
- element.style.position = 'sticky';
237
- element.style.top = `${nextTop}px`;
238
-
239
- stateItems.push({ element, top: nextTop });
240
- nextTop += element.offsetHeight + this.props.gap;
241
- }
242
-
243
- this.setState({
244
- items: stateItems,
245
- });
246
-
247
- if (typeof this.props.onRefresh === 'function') {
248
- this.props.onRefresh(this);
249
- }
250
-
251
- return nextTop;
252
- }
253
-
254
- private restore(): void {
255
- for (const item of this.cache.originalStyles) {
256
- item.element.style.position = item.originalPosition;
257
- item.element.style.top = item.originalTop;
258
- item.element.style.zIndex = item.originalZIndex;
259
- }
260
- }
261
-
262
- /**
263
- * 重新计算当前实例内所有 sticky 元素的 top。
264
- * @returns {Sticky} 当前实例。
265
- */
266
- refresh(): this {
267
- if (
268
- this.runtime.destroyed ||
269
- !this.runtime.built ||
270
- this.dom.targets.length === 0
271
- ) {
272
- return this;
273
- }
274
- this.apply();
275
- return this;
276
- }
277
-
278
- destroy(): void {
279
- if (this.runtime.destroyed) return;
280
- this.restore();
281
- super.destroy();
282
- }
283
-
284
- /**
285
- * 销毁实例并恢复被管理元素的原始样式。
286
- * @private
287
- */
288
- protected onDestroy(): void {
289
- this.runtime.built = false;
290
- this.cache.originalStyles = [];
291
- this.dom.targets = [];
292
- }
293
- }
294
-
295
- export function createSticky(props: StickyProps = {}): StickyInstance {
296
- return new Sticky(props);
297
- }
@@ -1,377 +0,0 @@
1
- import { createDeepStore, flushSync } from 'vanilla-signal';
2
-
3
- import { createEventManager } from '../utilities/events.ts';
4
- import { isPlainObject } from '../utilities/object.ts';
5
-
6
- export type ComponentProps = Record<string, unknown>;
7
- export type ComponentState = Record<string, unknown>;
8
- export interface ComponentDOM {
9
- root: Element | null;
10
- [key: string]: unknown;
11
- }
12
-
13
- export interface ComponentRuntime {
14
- destroyed: boolean;
15
- }
16
-
17
- export type ComponentCache = unknown;
18
-
19
- export interface ComponentReCreateOptions {
20
- force?: boolean;
21
- }
22
-
23
- export type ComponentLifecycleEvent =
24
- | 'init'
25
- | 'beforeReCreate'
26
- | 'afterReCreate'
27
- | 'destroy';
28
- export type ComponentEventName = string;
29
- export type ComponentListener = (...args: unknown[]) => void;
30
- export type ComponentListeners = Record<
31
- ComponentLifecycleEvent,
32
- ComponentListener[]
33
- > &
34
- Record<string, ComponentListener[] | undefined>;
35
-
36
- export type ComponentCleanup =
37
- | void
38
- | (() => void)
39
- | {
40
- destroy: () => void;
41
- };
42
-
43
- export type ComponentPluginOptions = Record<string, unknown> | undefined;
44
- export type ComponentPlugin<TComponent = Component> =
45
- | ((
46
- instance: TComponent,
47
- options?: ComponentPluginOptions
48
- ) => ComponentCleanup)
49
- | {
50
- install: (
51
- instance: TComponent,
52
- options?: ComponentPluginOptions
53
- ) => ComponentCleanup;
54
- };
55
-
56
- export interface ComponentCleanupRegistry {
57
- events: ReturnType<typeof createEventManager>;
58
- plugins: Map<ComponentPlugin<unknown>, ComponentCleanup>;
59
- [key: string]: unknown;
60
- }
61
-
62
- /**
63
- * 轻量级组件基类,集成 vanilla-signal 响应式状态和插件系统
64
- * 为所有 UI 组件提供统一的状态管理、生命周期钩子和插件支持
65
- */
66
- export default class Component<
67
- TProps extends ComponentProps = ComponentProps,
68
- TState extends ComponentState = ComponentState,
69
- TDOM extends ComponentDOM = ComponentDOM,
70
- TCache = ComponentCache,
71
- > {
72
- /** 全局插件注册表,所有新创建的组件实例会自动安装这些插件 */
73
- static globalPlugins = new Map<string, ComponentPlugin<Component>>();
74
-
75
- /** 组件属性配置对象 */
76
- props: TProps;
77
-
78
- /** DOM 引用容器,存储根元素及其他 DOM 节点引用 */
79
- dom: TDOM;
80
-
81
- /** 实例缓存容器,存储组件运行时派生数据 */
82
- cache: TCache;
83
-
84
- /** 已安装的插件映射表 */
85
- plugins: Map<ComponentPlugin<unknown>, ComponentCleanup>;
86
-
87
- /** 资源清理管理器,包含事件监听器和插件的清理函数 */
88
- cleanup: ComponentCleanupRegistry;
89
-
90
- /** 内部事件监听器注册表,用于生命周期和自定义事件 */
91
- protected _listeners: ComponentListeners;
92
-
93
- /** 响应式状态存储,使用 vanilla-signal 的深层响应式 store */
94
- state: TState | null;
95
-
96
- /** 运行时状态标记 */
97
- runtime: ComponentRuntime;
98
-
99
- /**
100
- * @param {Object} props - 组件初始属性配置
101
- */
102
- constructor(props: TProps = {} as TProps) {
103
- /** 组件属性配置对象 */
104
- this.props = (props || {}) as TProps;
105
-
106
- /** DOM 引用容器,存储根元素及其他 DOM 节点引用 */
107
- this.dom = { root: null } as TDOM;
108
-
109
- /** 实例缓存容器,存储组件运行时派生数据 */
110
- this.cache = {} as TCache;
111
-
112
- /** 已安装的插件映射表 */
113
- this.plugins = new Map();
114
-
115
- /** 资源清理管理器,包含事件监听器和插件的清理函数 */
116
- this.cleanup = { events: createEventManager(), plugins: new Map() };
117
-
118
- /** 内部事件监听器注册表,用于生命周期和自定义事件 */
119
- this._listeners = {
120
- init: [], // 初始化完成事件
121
- beforeReCreate: [], // 重新实例化前事件
122
- afterReCreate: [], // 重新实例化后事件
123
- destroy: [], // 销毁事件
124
- };
125
-
126
- /** 响应式状态存储,使用 vanilla-signal 的深层响应式 store */
127
- this.state = createDeepStore({} as TState);
128
-
129
- /** 运行时状态标记 */
130
- this.runtime = { destroyed: false };
131
-
132
- /** 自动安装全局插件 */
133
- this.installGlobalPlugins();
134
- }
135
-
136
- /**
137
- * 安装实例插件
138
- * 插件可以是函数形式 plugin(instance, options) 或对象形式 { install(instance, options) }
139
- * 插件应返回清理函数或清理对象,在组件销毁时自动执行
140
- * @param {Function|Object} plugin - 插件函数或包含 install 方法的插件对象
141
- * @param {Object} [options] - 插件配置选项
142
- * @returns {Component} 返回当前实例,支持链式调用
143
- */
144
- use(
145
- plugin: ComponentPlugin<this> | null | undefined,
146
- options?: ComponentPluginOptions
147
- ): this {
148
- if (!plugin) return this;
149
- const cleanup =
150
- typeof plugin === 'function'
151
- ? plugin(this, options)
152
- : plugin.install?.(this, options);
153
- this.cleanup.plugins.set(plugin as ComponentPlugin<unknown>, cleanup);
154
- return this;
155
- }
156
-
157
- /**
158
- * 注册事件监听器
159
- * 用于监听组件生命周期事件(init、beforeReCreate、afterReCreate、destroy)或自定义事件
160
- * @param {string} event - 事件名称
161
- * @param {Function} callback - 事件回调函数
162
- * @returns {Component} 返回当前实例,支持链式调用
163
- */
164
- on(event: ComponentEventName, callback: ComponentListener): this {
165
- if (!this._listeners[event] || typeof callback !== 'function') return this;
166
- this._listeners[event].push(callback);
167
- return this;
168
- }
169
-
170
- /**
171
- * 移除事件监听器
172
- * @param {string} event - 事件名称
173
- * @param {Function} callback - 要移除的回调函数引用
174
- * @returns {Component} 返回当前实例,支持链式调用
175
- */
176
- off(event: ComponentEventName, callback: ComponentListener): this {
177
- if (!this._listeners[event]) return this;
178
- this._listeners[event] = this._listeners[event].filter(
179
- (listener) => listener !== callback
180
- );
181
- return this;
182
- }
183
-
184
- /**
185
- * 安装全局插件到当前实例
186
- * 遍历全局插件注册表并依次安装
187
- * @private
188
- */
189
- installGlobalPlugins(): void {
190
- for (const plugin of Component.globalPlugins.values()) {
191
- this.use(plugin as ComponentPlugin<this>);
192
- }
193
- }
194
-
195
- /**
196
- * 注册全局插件
197
- * 所有之后创建的组件实例都会自动安装此插件
198
- * @static
199
- * @param {string} name - 插件名称标识
200
- * @param {Function|Object} plugin - 插件函数或插件对象
201
- */
202
- static useGlobal(
203
- name: string | null | undefined,
204
- plugin: ComponentPlugin<Component> | null | undefined
205
- ): void {
206
- if (!name || !plugin) return;
207
- Component.globalPlugins.set(name, plugin);
208
- }
209
-
210
- /**
211
- * 触发指定事件,执行所有注册的监听器
212
- * @param {string} event - 事件名称
213
- * @param {...*} args - 传递给监听器的参数
214
- * @returns {Component} 返回当前实例,支持链式调用
215
- */
216
- emit(event: ComponentEventName, ...args: unknown[]): this {
217
- const listeners = this._listeners[event];
218
- if (!listeners) return this;
219
- for (const listener of listeners) {
220
- try {
221
- listener(...args);
222
- } catch {
223
- // 忽略监听器执行错误,防止影响其他监听器
224
- }
225
- }
226
- return this;
227
- }
228
-
229
- /**
230
- * 初始化组件
231
- * 合并传入的属性,调用 onInit 钩子(如果存在),并触发 init 事件
232
- * @param {Object} [props={}] - 初始化属性配置
233
- * @returns {Component} 返回当前实例,支持链式调用
234
- * @throws {Error} 如果组件已被销毁则抛出异常
235
- */
236
- init(props: Partial<TProps> | null | undefined = {}): this {
237
- if (this.runtime.destroyed)
238
- throw new Error('Component.init: instance destroyed');
239
- if (isPlainObject(props)) {
240
- this.props = Object.assign({}, this.props, props);
241
- }
242
- if (typeof this.onInit === 'function') this.onInit(this.props);
243
- this.emit('init', this.props);
244
- return this;
245
- }
246
-
247
- /**
248
- * 批量更新响应式状态
249
- * 支持两种调用方式:setState(key, value) 或 setState({ key1: value1, key2: value2 })
250
- * 使用 flushSync 确保状态更新的同步性和批量处理
251
- * @param {string|Object} keyOrPatch - 状态键名或包含多个键值对的补丁对象
252
- * @param {*} [value] - 当第一个参数为字符串时的状态值
253
- * @returns {Component} 返回当前实例,支持链式调用
254
- * @throws {Error} 如果组件已被销毁或参数格式不正确则抛出异常
255
- */
256
- setState(patch?: Partial<TState> | null): this;
257
- setState<TKey extends keyof TState>(key: TKey, value: TState[TKey]): this;
258
- setState(
259
- keyOrPatch: Partial<TState> | keyof TState | null | undefined = {},
260
- value?: unknown
261
- ): this {
262
- if (this.runtime.destroyed)
263
- throw new Error('Component.setState: instance destroyed');
264
-
265
- const patch =
266
- typeof keyOrPatch === 'string' && arguments.length > 1
267
- ? { [keyOrPatch]: value }
268
- : keyOrPatch;
269
- if (!isPlainObject(patch)) {
270
- throw new Error('Component.setState: expects a plain object patch.');
271
- }
272
-
273
- const normalizedPatch = this.normalizeStatePatch(patch as Partial<TState>);
274
- if (!isPlainObject(normalizedPatch)) {
275
- throw new Error('Component.setState: expects a plain object patch.');
276
- }
277
- this.validateStatePatch(normalizedPatch);
278
-
279
- flushSync(() => {
280
- for (const [k, v] of Object.entries(normalizedPatch)) {
281
- if (!this.state) return;
282
- this.state[k as keyof TState] = v as TState[keyof TState];
283
- }
284
- });
285
- this.afterSetState(normalizedPatch);
286
- return this;
287
- }
288
-
289
- /**
290
- * 根据新的 props 重新创建组件实例并触发生命周期
291
- * 合并新的属性配置,触发 beforeReCreate 和 afterReCreate 事件
292
- * 子类可以重写 onReCreate 方法实现自定义重建前逻辑
293
- * @param {Object} [propsPatch={}] - 要合并的属性补丁对象
294
- * @param {Object} [options] - 更新选项
295
- * @param {boolean} [options.force=false] - 是否强制更新(由子类处理)
296
- * @returns {Component} 返回新创建的实例
297
- * @throws {Error} 如果组件已被销毁则抛出异常
298
- */
299
- reCreate(
300
- propsPatch: Partial<TProps> | null | undefined = {},
301
- { force = false }: ComponentReCreateOptions = {}
302
- ): this {
303
- if (this.runtime.destroyed)
304
- throw new Error('Component.reCreate: instance destroyed');
305
-
306
- if (!isPlainObject(propsPatch)) return this;
307
-
308
- const nextProps = Object.assign({}, this.props, propsPatch);
309
- this.emit('beforeReCreate', propsPatch, { force });
310
- if (typeof this.onReCreate === 'function')
311
- this.onReCreate(propsPatch, { force });
312
-
313
- const nextInstance = this.createInstance(nextProps);
314
- this.destroy();
315
- nextInstance.emit('afterReCreate', propsPatch, { force });
316
- return nextInstance;
317
- }
318
-
319
- protected createInstance(props: TProps): this {
320
- const ComponentConstructor = this.constructor as new (
321
- props: TProps
322
- ) => this;
323
- return new ComponentConstructor(props);
324
- }
325
-
326
- /**
327
- * 销毁组件实例
328
- * 执行 onDestroy 钩子,触发 destroy 事件,清理所有插件和资源
329
- * 这是组件生命周期的最后一步,销毁后实例不可再使用
330
- */
331
- destroy(): void {
332
- if (this.runtime.destroyed) return;
333
- this.runtime.destroyed = true;
334
-
335
- if (typeof this.onDestroy === 'function') this.onDestroy();
336
- this.emit('destroy');
337
-
338
- // 执行所有插件的清理函数
339
- for (const cleanup of this.cleanup.plugins.values()) {
340
- try {
341
- if (typeof cleanup === 'function') cleanup();
342
- else if (cleanup && typeof cleanup.destroy === 'function')
343
- cleanup.destroy();
344
- } catch {
345
- // 忽略清理函数执行错误
346
- }
347
- }
348
- this.cleanup.plugins.clear();
349
- this.cleanup.events.clear();
350
- this.dom = { root: null } as TDOM;
351
- this.cache = {} as TCache;
352
- this.state = null;
353
- }
354
-
355
- protected onInit?(props: TProps): void;
356
- protected onReCreate?(
357
- propsPatch: Partial<TProps> | null | undefined,
358
- options: Required<ComponentReCreateOptions>
359
- ): void;
360
- protected normalizeStatePatch(patch: Partial<TState>): Partial<TState> {
361
- return patch;
362
- }
363
- protected validateStatePatch(patch: Partial<TState>): void {
364
- if (!this.state) return;
365
- for (const key of Object.keys(patch)) {
366
- const stateKey = String(key);
367
- const exists = Object.hasOwn(this.state as object, stateKey);
368
- if (!exists) {
369
- throw new Error(
370
- `Component.setState: "${stateKey}" is not a supported state key.`
371
- );
372
- }
373
- }
374
- }
375
- protected afterSetState(_patch: Partial<TState>): void {}
376
- protected onDestroy?(): void;
377
- }