vanilla-jui 1.3.10 → 1.4.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 (90) hide show
  1. package/dist/index.d.ts +2927 -0
  2. package/dist/index.js +1 -1
  3. package/dist/index.umd.js +1 -1
  4. package/dist/style.css +1 -1
  5. package/package.json +4 -3
  6. package/src/components/accordion.ts +522 -0
  7. package/src/components/drop.ts +497 -0
  8. package/src/components/flow.ts +1579 -0
  9. package/src/components/form.ts +953 -0
  10. package/src/components/{icons.js → icons.ts} +24 -11
  11. package/src/components/menu.ts +615 -0
  12. package/src/components/{modal.js → modal.ts} +560 -237
  13. package/src/components/offcanvas.ts +496 -0
  14. package/src/components/{pagination.js → pagination.ts} +251 -76
  15. package/src/components/parabola.ts +335 -0
  16. package/src/components/{sticky.js → sticky.ts} +123 -98
  17. package/src/components/{swiper.js → swiper.ts} +510 -232
  18. package/src/components/tabs.ts +984 -0
  19. package/src/components/theme.ts +437 -0
  20. package/src/components/toast.ts +371 -0
  21. package/src/components/toc.ts +385 -0
  22. package/src/components/tooltip.ts +179 -0
  23. package/src/components/validator.ts +596 -0
  24. package/src/core/{Component.js → Component.ts} +134 -27
  25. package/src/css/accordion.css +2 -2
  26. package/src/css/animation.css +31 -1
  27. package/src/css/breadcrumb.css +1 -1
  28. package/src/css/button.css +5 -5
  29. package/src/css/card.css +7 -7
  30. package/src/css/color.css +865 -1028
  31. package/src/css/color_p3.css +871 -1036
  32. package/src/css/components.css +1 -0
  33. package/src/css/drop.css +1 -1
  34. package/src/css/flow.css +1 -1
  35. package/src/css/form.css +21 -21
  36. package/src/css/gradient.css +17 -4
  37. package/src/css/icon.css +17 -1
  38. package/src/css/index.css +3 -3
  39. package/src/css/layout.css +1 -1
  40. package/src/css/menu.css +27 -27
  41. package/src/css/modal.css +10 -27
  42. package/src/css/offcanvas.css +2 -2
  43. package/src/css/pagination.css +1 -1
  44. package/src/css/popup.css +2 -2
  45. package/src/css/swiper.css +12 -12
  46. package/src/css/tabs.css +3 -2
  47. package/src/css/tag.css +3 -4
  48. package/src/css/theme-palette.css +6 -6
  49. package/src/css/tip.css +1 -1
  50. package/src/css/toast.css +8 -8
  51. package/src/css/tokens.css +197 -226
  52. package/src/css/toolbar.css +3 -3
  53. package/src/css/tooltip.css +1 -1
  54. package/src/index.ts +37 -0
  55. package/src/locales/{index.js → index.ts} +1 -2
  56. package/src/types.d.ts +1 -0
  57. package/src/utilities/browser.ts +131 -0
  58. package/src/utilities/cache.ts +57 -0
  59. package/src/utilities/core.ts +614 -0
  60. package/src/utilities/dom.ts +468 -0
  61. package/src/utilities/events.ts +106 -0
  62. package/src/utilities/http.ts +35 -0
  63. package/src/utilities/storage.ts +46 -0
  64. package/src/components/accordion.js +0 -373
  65. package/src/components/drop.js +0 -437
  66. package/src/components/flow.js +0 -1133
  67. package/src/components/form.js +0 -585
  68. package/src/components/menu.js +0 -422
  69. package/src/components/offcanvas.js +0 -332
  70. package/src/components/parabola.js +0 -295
  71. package/src/components/tabs.js +0 -746
  72. package/src/components/theme.js +0 -353
  73. package/src/components/toast.js +0 -329
  74. package/src/components/toc.js +0 -226
  75. package/src/components/tooltip.js +0 -86
  76. package/src/components/validator.js +0 -586
  77. package/src/css/toast.min.css +0 -1
  78. package/src/index.js +0 -45
  79. package/src/locales/index.min.js +0 -1
  80. package/src/utilities/browser.js +0 -111
  81. package/src/utilities/cache.js +0 -159
  82. package/src/utilities/core.js +0 -502
  83. package/src/utilities/debug.js +0 -103
  84. package/src/utilities/dom.js +0 -313
  85. package/src/utilities/events.js +0 -69
  86. package/src/utilities/http.js +0 -30
  87. package/src/utilities/legacy-dom.js +0 -179
  88. package/src/utilities/storage.js +0 -46
  89. /package/src/{components/components.js → css/themes/index.css} +0 -0
  90. /package/src/icons/{index.js → index.ts} +0 -0
@@ -1,24 +1,109 @@
1
1
  import { createDeepStore, flushSync } from 'vanilla-signal';
2
2
 
3
- import { createEventManager } from '../utilities/events.js';
3
+ import { createEventManager } from '../utilities/events.ts';
4
+
5
+ export type ComponentProps = Record<string, unknown>;
6
+ export type ComponentState = Record<string, unknown>;
7
+ export interface ComponentDOM {
8
+ root: Element | null;
9
+ [key: string]: unknown;
10
+ }
11
+
12
+ export interface ComponentRuntime {
13
+ destroyed: boolean;
14
+ }
15
+
16
+ export interface ComponentUpdateOptions {
17
+ force?: boolean;
18
+ }
19
+
20
+ export type ComponentLifecycleEvent =
21
+ | 'init'
22
+ | 'beforeUpdate'
23
+ | 'afterUpdate'
24
+ | 'destroy';
25
+ export type ComponentEventName = string;
26
+ export type ComponentListener = (...args: unknown[]) => void;
27
+ export type ComponentListeners = Record<
28
+ ComponentLifecycleEvent,
29
+ ComponentListener[]
30
+ > &
31
+ Record<string, ComponentListener[] | undefined>;
32
+
33
+ export type ComponentCleanup =
34
+ | void
35
+ | (() => void)
36
+ | {
37
+ destroy: () => void;
38
+ };
39
+
40
+ export type ComponentPluginOptions = Record<string, unknown> | undefined;
41
+ export type ComponentPlugin<TComponent = Component> =
42
+ | ((
43
+ instance: TComponent,
44
+ options?: ComponentPluginOptions
45
+ ) => ComponentCleanup)
46
+ | {
47
+ install: (
48
+ instance: TComponent,
49
+ options?: ComponentPluginOptions
50
+ ) => ComponentCleanup;
51
+ };
52
+
53
+ export interface ComponentCleanupRegistry {
54
+ events: ReturnType<typeof createEventManager>;
55
+ plugins: Map<ComponentPlugin<unknown>, ComponentCleanup>;
56
+ [key: string]: unknown;
57
+ }
58
+
59
+ function isPlainObject(value: unknown): value is ComponentProps {
60
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
61
+ const proto = Object.getPrototypeOf(value);
62
+ return proto === Object.prototype || proto === null;
63
+ }
4
64
 
5
65
  /**
6
66
  * 轻量级组件基类,集成 vanilla-signal 响应式状态和插件系统
7
67
  * 为所有 UI 组件提供统一的状态管理、生命周期钩子和插件支持
8
68
  */
9
- export default class Component {
69
+ export default class Component<
70
+ TProps extends ComponentProps = ComponentProps,
71
+ TState extends ComponentState = ComponentState,
72
+ TDOM extends ComponentDOM = ComponentDOM,
73
+ > {
10
74
  /** 全局插件注册表,所有新创建的组件实例会自动安装这些插件 */
11
- static globalPlugins = new Map();
75
+ static globalPlugins = new Map<string, ComponentPlugin<Component>>();
76
+
77
+ /** 组件属性配置对象 */
78
+ props: TProps;
79
+
80
+ /** DOM 引用容器,存储根元素及其他 DOM 节点引用 */
81
+ dom: TDOM;
82
+
83
+ /** 已安装的插件映射表 */
84
+ plugins: Map<ComponentPlugin<unknown>, ComponentCleanup>;
85
+
86
+ /** 资源清理管理器,包含事件监听器和插件的清理函数 */
87
+ cleanup: ComponentCleanupRegistry;
88
+
89
+ /** 内部事件监听器注册表,用于生命周期和自定义事件 */
90
+ protected _listeners: ComponentListeners;
91
+
92
+ /** 响应式状态存储,使用 vanilla-signal 的深层响应式 store */
93
+ state: TState | null;
94
+
95
+ /** 运行时状态标记 */
96
+ runtime: ComponentRuntime;
12
97
 
13
98
  /**
14
99
  * @param {Object} props - 组件初始属性配置
15
100
  */
16
- constructor(props = {}) {
101
+ constructor(props: TProps = {} as TProps) {
17
102
  /** 组件属性配置对象 */
18
- this.props = props || {};
103
+ this.props = (props || {}) as TProps;
19
104
 
20
105
  /** DOM 引用容器,存储根元素及其他 DOM 节点引用 */
21
- this.dom = { root: null };
106
+ this.dom = { root: null } as TDOM;
22
107
 
23
108
  /** 已安装的插件映射表 */
24
109
  this.plugins = new Map();
@@ -35,7 +120,7 @@ export default class Component {
35
120
  };
36
121
 
37
122
  /** 响应式状态存储,使用 vanilla-signal 的深层响应式 store */
38
- this.state = createDeepStore({});
123
+ this.state = createDeepStore({} as TState);
39
124
 
40
125
  /** 运行时状态标记 */
41
126
  this.runtime = { destroyed: false };
@@ -48,7 +133,7 @@ export default class Component {
48
133
  * 获取组件的根 DOM 元素
49
134
  * @returns {HTMLElement|null} 根 DOM 元素或 null
50
135
  */
51
- get root() {
136
+ get root(): Element | null {
52
137
  return this.dom?.root || null;
53
138
  }
54
139
 
@@ -56,8 +141,8 @@ export default class Component {
56
141
  * 设置组件的根 DOM 元素
57
142
  * @param {HTMLElement} value - 要设置的根 DOM 元素
58
143
  */
59
- set root(value) {
60
- if (!this.dom) this.dom = {};
144
+ set root(value: Element | null) {
145
+ if (!this.dom) this.dom = { root: null } as TDOM;
61
146
  this.dom.root = value;
62
147
  }
63
148
 
@@ -69,13 +154,16 @@ export default class Component {
69
154
  * @param {Object} [options] - 插件配置选项
70
155
  * @returns {Component} 返回当前实例,支持链式调用
71
156
  */
72
- use(plugin, options) {
157
+ use(
158
+ plugin: ComponentPlugin<this> | null | undefined,
159
+ options?: ComponentPluginOptions
160
+ ): this {
73
161
  if (!plugin) return this;
74
162
  const cleanup =
75
163
  typeof plugin === 'function'
76
164
  ? plugin(this, options)
77
165
  : plugin.install?.(this, options);
78
- this.cleanup.plugins.set(plugin, cleanup);
166
+ this.cleanup.plugins.set(plugin as ComponentPlugin<unknown>, cleanup);
79
167
  return this;
80
168
  }
81
169
 
@@ -86,7 +174,7 @@ export default class Component {
86
174
  * @param {Function} callback - 事件回调函数
87
175
  * @returns {Component} 返回当前实例,支持链式调用
88
176
  */
89
- on(event, callback) {
177
+ on(event: ComponentEventName, callback: ComponentListener): this {
90
178
  if (!this._listeners[event] || typeof callback !== 'function') return this;
91
179
  this._listeners[event].push(callback);
92
180
  return this;
@@ -98,7 +186,7 @@ export default class Component {
98
186
  * @param {Function} callback - 要移除的回调函数引用
99
187
  * @returns {Component} 返回当前实例,支持链式调用
100
188
  */
101
- off(event, callback) {
189
+ off(event: ComponentEventName, callback: ComponentListener): this {
102
190
  if (!this._listeners[event]) return this;
103
191
  this._listeners[event] = this._listeners[event].filter(
104
192
  (listener) => listener !== callback
@@ -111,9 +199,9 @@ export default class Component {
111
199
  * 遍历全局插件注册表并依次安装
112
200
  * @private
113
201
  */
114
- installGlobalPlugins() {
202
+ installGlobalPlugins(): void {
115
203
  for (const plugin of Component.globalPlugins.values()) {
116
- this.use(plugin);
204
+ this.use(plugin as ComponentPlugin<this>);
117
205
  }
118
206
  }
119
207
 
@@ -124,7 +212,10 @@ export default class Component {
124
212
  * @param {string} name - 插件名称标识
125
213
  * @param {Function|Object} plugin - 插件函数或插件对象
126
214
  */
127
- static useGlobal(name, plugin) {
215
+ static useGlobal(
216
+ name: string | null | undefined,
217
+ plugin: ComponentPlugin<Component> | null | undefined
218
+ ): void {
128
219
  if (!name || !plugin) return;
129
220
  Component.globalPlugins.set(name, plugin);
130
221
  }
@@ -135,7 +226,7 @@ export default class Component {
135
226
  * @param {...*} args - 传递给监听器的参数
136
227
  * @returns {Component} 返回当前实例,支持链式调用
137
228
  */
138
- emit(event, ...args) {
229
+ emit(event: ComponentEventName, ...args: unknown[]): this {
139
230
  const listeners = this._listeners[event];
140
231
  if (!listeners) return this;
141
232
  for (const listener of listeners) {
@@ -155,10 +246,10 @@ export default class Component {
155
246
  * @returns {Component} 返回当前实例,支持链式调用
156
247
  * @throws {Error} 如果组件已被销毁则抛出异常
157
248
  */
158
- init(props = {}) {
249
+ init(props: Partial<TProps> | null | undefined = {}): this {
159
250
  if (this.runtime.destroyed)
160
251
  throw new Error('Component.init: instance destroyed');
161
- if (props && typeof props === 'object' && !Array.isArray(props)) {
252
+ if (isPlainObject(props)) {
162
253
  this.props = Object.assign({}, this.props, props);
163
254
  }
164
255
  if (typeof this.onInit === 'function') this.onInit(this.props);
@@ -175,7 +266,12 @@ export default class Component {
175
266
  * @returns {Component} 返回当前实例,支持链式调用
176
267
  * @throws {Error} 如果组件已被销毁或参数格式不正确则抛出异常
177
268
  */
178
- setState(keyOrPatch = {}, value) {
269
+ setState(patch?: Partial<TState> | null): this;
270
+ setState<TKey extends keyof TState>(key: TKey, value: TState[TKey]): this;
271
+ setState(
272
+ keyOrPatch: Partial<TState> | keyof TState | null | undefined = {},
273
+ value?: unknown
274
+ ): this {
179
275
  if (this.runtime.destroyed)
180
276
  throw new Error('Component.setState: instance destroyed');
181
277
 
@@ -183,13 +279,14 @@ export default class Component {
183
279
  typeof keyOrPatch === 'string' && arguments.length > 1
184
280
  ? { [keyOrPatch]: value }
185
281
  : keyOrPatch;
186
- if (!patch || typeof patch !== 'object' || Array.isArray(patch)) {
282
+ if (!isPlainObject(patch)) {
187
283
  throw new Error('Component.setState: expects a plain object patch.');
188
284
  }
189
285
 
190
286
  flushSync(() => {
191
287
  for (const [k, v] of Object.entries(patch)) {
192
- this.state[k] = v;
288
+ if (!this.state) return;
289
+ this.state[k as keyof TState] = v as TState[keyof TState];
193
290
  }
194
291
  });
195
292
  return this;
@@ -205,10 +302,13 @@ export default class Component {
205
302
  * @returns {Component} 返回当前实例,支持链式调用
206
303
  * @throws {Error} 如果组件已被销毁则抛出异常
207
304
  */
208
- update(propsPatch = {}, { force = false } = {}) {
305
+ update(
306
+ propsPatch: Partial<TProps> | null | undefined = {},
307
+ { force = false }: ComponentUpdateOptions = {}
308
+ ): this {
209
309
  if (this.runtime.destroyed)
210
310
  throw new Error('Component.update: instance destroyed');
211
- if (propsPatch && typeof propsPatch === 'object') {
311
+ if (isPlainObject(propsPatch)) {
212
312
  this.props = Object.assign({}, this.props, propsPatch);
213
313
  }
214
314
  this.emit('beforeUpdate', propsPatch, { force });
@@ -223,7 +323,7 @@ export default class Component {
223
323
  * 执行 onDestroy 钩子,触发 destroy 事件,清理所有插件和资源
224
324
  * 这是组件生命周期的最后一步,销毁后实例不可再使用
225
325
  */
226
- destroy() {
326
+ destroy(): void {
227
327
  if (this.runtime.destroyed) return;
228
328
  this.runtime = { destroyed: true };
229
329
 
@@ -242,7 +342,14 @@ export default class Component {
242
342
  }
243
343
  this.cleanup.plugins.clear();
244
344
  this.cleanup.events.clear();
245
- this.dom = { root: null };
345
+ this.dom = { root: null } as TDOM;
246
346
  this.state = null;
247
347
  }
348
+
349
+ protected onInit?(props: TProps): void;
350
+ protected onUpdate?(
351
+ propsPatch: Partial<TProps> | null | undefined,
352
+ options: Required<ComponentUpdateOptions>
353
+ ): void;
354
+ protected onDestroy?(): void;
248
355
  }
@@ -9,7 +9,7 @@
9
9
  align-items: center;
10
10
  cursor: pointer;
11
11
  padding: var(--accordion-header-padding,
12
- calc(var(--space) * 2) calc(var(--space) * 3));
12
+ var(--s2) var(--s3));
13
13
  margin: var(--accordion-header-margin, 0);
14
14
  border-bottom: var(--accordion-border);
15
15
  font-size: var(--accordion-header-font-size, var(--font-size-md));
@@ -61,7 +61,7 @@
61
61
  .j-accordion .accordion-panel .panel-content {
62
62
  margin: var(--accordion-panel-content-margin, 0);
63
63
  padding: var(--accordion-panel-content-padding,
64
- 0 calc(var(--space) * 3) calc(var(--space) * 2));
64
+ 0 var(--s3) var(--s2));
65
65
  border-bottom: var(--accordion-border);
66
66
  font-size: var(--accordion-font-size, var(--font-size-md));
67
67
  }
@@ -1,4 +1,5 @@
1
1
  @layer animation {
2
+
2
3
  /* none */
3
4
  .animate-none {
4
5
  animation: none;
@@ -21,6 +22,7 @@
21
22
  }
22
23
 
23
24
  @keyframes animate-ping {
25
+
24
26
  75%,
25
27
  100% {
26
28
  transform: scale(2);
@@ -45,6 +47,7 @@
45
47
  }
46
48
 
47
49
  @keyframes animate-bounce {
50
+
48
51
  0%,
49
52
  100% {
50
53
  transform: translateY(-25%);
@@ -97,6 +100,7 @@
97
100
  }
98
101
 
99
102
  @keyframes animate-shake {
103
+
100
104
  0%,
101
105
  100% {
102
106
  transform: translateX(0);
@@ -174,4 +178,30 @@
174
178
  transform: translateX(-100%);
175
179
  }
176
180
  }
177
- }
181
+
182
+ /* reveal */
183
+ [data-reveal] {
184
+ opacity: 0;
185
+ transform: translateY(12px);
186
+ animation: reveal 0.62s ease forwards;
187
+ }
188
+
189
+ [data-reveal='2'] {
190
+ animation-delay: 0.08s;
191
+ }
192
+
193
+ [data-reveal='3'] {
194
+ animation-delay: 0.14s;
195
+ }
196
+
197
+ [data-reveal='4'] {
198
+ animation-delay: 0.2s;
199
+ }
200
+
201
+ @keyframes reveal {
202
+ to {
203
+ opacity: 1;
204
+ transform: translateY(0);
205
+ }
206
+ }
207
+ }
@@ -74,7 +74,7 @@
74
74
  justify-content: center;
75
75
  gap: var(--space);
76
76
  color: var(--breadcrumb-link-color, var(--tone-text));
77
- padding: var(--breadcrumb-item-padding-vertical, 0) var(--breadcrumb-item-padding-horizontal, calc(var(--space) * 2));
77
+ padding: var(--breadcrumb-item-padding-vertical, 0) var(--breadcrumb-item-padding-horizontal, var(--s2));
78
78
  text-decoration: none;
79
79
  }
80
80
 
@@ -4,7 +4,7 @@
4
4
  justify-content: center;
5
5
  align-items: center;
6
6
  flex-wrap: nowrap;
7
- gap: var(--button-gap, calc(var(--space) * 1) calc(var(--space) * 1.5));
7
+ gap: var(--button-gap, var(--space) calc(var(--space) * 1.5));
8
8
  position: relative;
9
9
  white-space: nowrap;
10
10
  font-weight: var(--button-font-weight, var(--font-medium));
@@ -12,7 +12,7 @@
12
12
  height: var(--button-height, var(--el-height-md));
13
13
  line-height: var(--button-line-height, var(--line-height-self));
14
14
  background-color: var(--button-background, transparent);
15
- padding: var(--button-padding, 0 calc(var(--space) * 3));
15
+ padding: var(--button-padding, 0 var(--s3));
16
16
  border-width: var(--button-border-width, 1px);
17
17
  border-style: var(--button-border-style, solid);
18
18
  border-color: var(--button-border-color, transparent);
@@ -42,13 +42,13 @@
42
42
  .j-button.is-sm {
43
43
  font-size: var(--font-size-sm);
44
44
  height: var(--button-height-sm, var(--el-height-sm));
45
- padding: var(--button-padding-sm, calc(var(--space) * 1) calc(var(--space) * 2));
45
+ padding: var(--button-padding-sm, var(--space) var(--s2));
46
46
  }
47
47
 
48
48
  .j-button.is-lg {
49
49
  font-size: var(--font-size-lg);
50
50
  height: var(--button-height-lg, var(--el-height-lg));
51
- padding: var(--button-padding-lg, calc(var(--space) * 3) calc(var(--space) * 4));
51
+ padding: var(--button-padding-lg, var(--s3) var(--s4));
52
52
  }
53
53
 
54
54
  /* variant */
@@ -136,7 +136,7 @@
136
136
 
137
137
  /* group */
138
138
  .j-button-group {
139
- --button-padding: 0 calc(var(--space) * 2);
139
+ --button-padding: 0 var(--s2);
140
140
  display: flex;
141
141
  margin: 0;
142
142
  }
package/src/css/card.css CHANGED
@@ -29,7 +29,7 @@
29
29
  color: var(--card-header-color, var(--ui-fg));
30
30
  font-size: var(--font-size-md);
31
31
  font-weight: var(--card-header-weight, var(--font-medium));
32
- padding: var(--card-header-padding, calc(var(--space) * 3) calc(var(--space) * 4));
32
+ padding: var(--card-header-padding, var(--s3) var(--s4));
33
33
  margin: 0;
34
34
  }
35
35
 
@@ -43,7 +43,7 @@
43
43
  border: none;
44
44
  margin: 0;
45
45
  padding: 0;
46
- padding: var(--card-header-padding, calc(var(--space) * 1) calc(var(--space) * 2));
46
+ padding: var(--card-header-padding, var(--space) var(--s2));
47
47
  appearance: none;
48
48
  cursor: pointer;
49
49
  }
@@ -84,7 +84,7 @@
84
84
 
85
85
  .j-card .card-content {
86
86
  background-color: var(--card-content-background);
87
- padding: var(--card-content-padding, calc(var(--space) * 4));
87
+ padding: var(--card-content-padding, var(--s4));
88
88
  line-height: var(--line-height-paragraph);
89
89
  }
90
90
 
@@ -118,8 +118,8 @@
118
118
  .j-card.is-vertical .card-header {
119
119
  flex-direction: column;
120
120
  align-items: flex-start;
121
- gap: calc(var(--space) * 2);
122
- padding: var(--card-header-padding, calc(var(--space) * 4) calc(var(--space) * 4) 0);
121
+ gap: var(--s2);
122
+ padding: var(--card-header-padding, var(--s4) var(--s4) 0);
123
123
  }
124
124
 
125
125
  .j-card.is-vertical .card-header .header-title {
@@ -138,7 +138,7 @@
138
138
 
139
139
  .j-card .card-footer {
140
140
  border-top: none;
141
- padding: var(--vertical-card-footer-padding, calc(var(--space) * 4));
141
+ padding: var(--vertical-card-footer-padding, var(--s4));
142
142
  }
143
143
 
144
144
  .j-card .card-content+.card-footer {
@@ -146,6 +146,6 @@
146
146
  }
147
147
 
148
148
  .j-card .card-footer:has(.j-button-group) {
149
- padding-top: 2px;
149
+ padding-top: 4px;
150
150
  }
151
151
  }