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,437 +0,0 @@
1
- import { jsx } from 'vanilla-signal';
2
-
3
- import { randomId, resolveProps } from '../utilities/core.js';
4
- import {
5
- isNode,
6
- isRenderableContent,
7
- normalizeContentNodes,
8
- requireRenderDOM,
9
- resolveElement,
10
- } from '../utilities/dom.js';
11
- import { createEventManager } from '../utilities/events.js';
12
-
13
- const DROP_PROPS_SCHEMA = {
14
- name: { default: null, types: ['string', 'null'] },
15
- mode: { default: 'click', type: 'string', enum: ['hover', 'click'] },
16
- position: {
17
- default: 'auto',
18
- type: 'string',
19
- enum: [
20
- 'auto',
21
- 'top-left',
22
- 'top-center',
23
- 'top-right',
24
- 'bottom-left',
25
- 'bottom-center',
26
- 'bottom-right',
27
- 'left',
28
- 'right',
29
- ],
30
- },
31
- offset: {
32
- default: 10,
33
- type: 'number',
34
- validate: (value) => value >= 0,
35
- message: 'expects a positive number or 0.',
36
- },
37
- content: {
38
- default: '',
39
- validate: isRenderableContent,
40
- message: 'expects string, Node, array, function or null.',
41
- },
42
- className: { default: null, types: ['string', 'null'] },
43
- id: {
44
- default: null,
45
- types: ['string', 'null'],
46
- normalize: (value) => {
47
- if (typeof value === 'string')
48
- return value.trim() ? value.trim() : randomId();
49
- if (value == null) return randomId();
50
- return value;
51
- },
52
- },
53
- containerClassName: { default: null, types: ['string', 'null'] },
54
- delay: { default: 0, types: ['number', 'object'] },
55
- hoverIntent: { default: true, type: 'boolean' },
56
- onShown: { default: null, types: ['function', 'null'] },
57
- onHidden: { default: null, types: ['function', 'null'] },
58
- };
59
-
60
- /**
61
- * @typedef {object} DropProps
62
- * @property {string|null} [name] 浮层名称,写入 data-drop。
63
- * @property {"hover"|"click"} [mode="click"] 触发方式。
64
- * @property {"auto"|"top-left"|"top-center"|"top-right"|"bottom-left"|"bottom-center"|"bottom-right"|"left"|"right"} [position="auto"] 浮层位置。
65
- * @property {number} [offset=10] 浮层与触发元素的间距。
66
- * @property {string|Node|Node[]|Function|null} [content=""] 浮层内容。
67
- * @property {string|null} [className] 浮层额外类名。
68
- * @property {string|null} [id] 浮层 id,不传时自动生成。
69
- * @property {string|null} [containerClassName] 内容容器额外类名。
70
- * @property {number|{show?:number,hide?:number}} [delay=0] 展示/隐藏延迟。
71
- * @property {boolean} [hoverIntent=true] hover 模式下是否启用意图判断,减少误触发。
72
- * @property {Function|null} [onShown] 展示后回调。
73
- * @property {Function|null} [onHidden] 隐藏后回调。
74
- */
75
-
76
- /**
77
- * 通用浮层组件。
78
- *
79
- * 可用于菜单、提示、下拉面板等场景,支持点击或 hover 触发,并自动计算视口内位置。
80
- */
81
- export class Drop {
82
- /**
83
- * 创建浮层实例。
84
- * @param {Element|Node|string|Array} element 触发元素、选择器或 JSX/h 返回节点。
85
- * @param {DropProps} [options={}] 浮层配置。
86
- */
87
- constructor(element, options = {}) {
88
- requireRenderDOM('Drop');
89
-
90
- this.target = resolveElement(element, 'Drop.element');
91
- this.props = resolveProps(options, DROP_PROPS_SCHEMA, 'Drop');
92
- this._init(this.props);
93
- }
94
-
95
- _init(props) {
96
- this.isVisible = false;
97
- this.root = null;
98
- this.cleanup = {
99
- events: createEventManager(),
100
- };
101
- this._timer = { show: null, hide: null };
102
- this._hoverIntentData = { x: 0, y: 0, lastMoveTime: 0 };
103
-
104
- const { delay } = props;
105
- if (typeof delay === 'number' && delay >= 0) {
106
- this.delayShow = delay;
107
- this.delayHide = delay;
108
- } else if (typeof delay === 'object' && delay !== null) {
109
- this.delayShow = Number(delay.show) || 0;
110
- this.delayHide = Number(delay.hide) || 0;
111
- } else {
112
- this.delayShow = 0;
113
- this.delayHide = 0;
114
- }
115
-
116
- this._buildDrop(props);
117
- this._bindEvents(props);
118
- }
119
-
120
- /**
121
- * 创建浮层 DOM(不挂载到 document)。
122
- * @private
123
- */
124
- _buildDrop(props) {
125
- const { className, content, id, name, containerClassName } = props;
126
- const _class = className || '';
127
- const _wrapper =
128
- isNode(content) && content.nodeType === 1
129
- ? content
130
- : jsx('div', {
131
- className: containerClassName
132
- ? `drop-container ${containerClassName}`
133
- : 'drop-container',
134
- children: normalizeContentNodes(content, this),
135
- });
136
-
137
- this.root = jsx('div', {
138
- className: _class ? `j-drop ${_class}` : 'j-drop',
139
- id: id,
140
- 'data-drop': name || randomId(),
141
- children: _wrapper,
142
- });
143
- }
144
-
145
- /**
146
- * 按触发模式绑定事件(仅 target 和 document)。
147
- * @private
148
- */
149
- _bindEvents(props) {
150
- const { mode, hoverIntent } = props;
151
- this._unbindEvents();
152
-
153
- if (mode === 'hover') {
154
- if (hoverIntent) {
155
- this.cleanup.events.on('target:enter', this.target, 'mouseenter', () =>
156
- this._startHoverIntent()
157
- );
158
- this.cleanup.events.on('target:leave', this.target, 'mouseleave', () =>
159
- this._cancelHoverIntent()
160
- );
161
- } else {
162
- this.cleanup.events.on('target:enter', this.target, 'mouseenter', () =>
163
- this.show()
164
- );
165
- this.cleanup.events.on('target:leave', this.target, 'mouseleave', () =>
166
- this.hide()
167
- );
168
- }
169
- } else if (mode === 'click') {
170
- this.cleanup.events.on('target:click', this.target, 'click', () =>
171
- this.toggle()
172
- );
173
- this.cleanup.events.on('document:click', document, 'click', (event) =>
174
- this._docClick(event)
175
- );
176
- }
177
- }
178
-
179
- /**
180
- * 绑定 root 浮层的 hover 事件(show 时调用)。
181
- * @private
182
- */
183
- _bindRootEvents() {
184
- if (!this.root || this.props.mode !== 'hover') return;
185
- this.cleanup.events.on('root:enter', this.root, 'mouseenter', () =>
186
- this.show()
187
- );
188
- this.cleanup.events.on('root:leave', this.root, 'mouseleave', () =>
189
- this.hide()
190
- );
191
- }
192
-
193
- /**
194
- * 解绑 root 浮层的 hover 事件(hide 时调用)。
195
- * @private
196
- */
197
- _unbindRootEvents() {
198
- this.cleanup.events.off('root:enter');
199
- this.cleanup.events.off('root:leave');
200
- }
201
-
202
- /**
203
- * 解绑当前实例的触发事件。
204
- * @private
205
- * @returns {void}
206
- */
207
- _unbindEvents() {
208
- this.cleanup?.events.clear();
209
- }
210
-
211
- /**
212
- * 启动 hoverIntent 检测。
213
- * @private
214
- * @returns {void}
215
- */
216
- _startHoverIntent() {
217
- this.cleanup.events.on('document:mousemove', document, 'mousemove', (e) =>
218
- this._onMouseMove(e)
219
- );
220
- clearTimeout(this._timer.show);
221
- this._timer.show = setTimeout(() => {
222
- const now = Date.now();
223
- const dt = now - this._hoverIntentData.lastMoveTime;
224
- const dx = Math.abs(this._hoverIntentData.x - this._lastX || 0);
225
- const dy = Math.abs(this._hoverIntentData.y - this._lastY || 0);
226
- const dist = dx + dy;
227
- if (dist < 5 || dt > 100) {
228
- this.show();
229
- this.cleanup.events.off('document:mousemove');
230
- } else {
231
- // 继续检测 hover 意图。
232
- this._startHoverIntent();
233
- }
234
- }, this.delayShow);
235
- }
236
-
237
- /**
238
- * 取消 hoverIntent 检测并隐藏浮层。
239
- * @private
240
- * @returns {void}
241
- */
242
- _cancelHoverIntent() {
243
- clearTimeout(this._timer.show);
244
- this.cleanup.events.off('document:mousemove');
245
- this.hide();
246
- }
247
-
248
- /**
249
- * 记录 hoverIntent 所需的鼠标移动状态。
250
- * @private
251
- * @param {MouseEvent} e 鼠标移动事件。
252
- * @returns {void}
253
- */
254
- _onMouseMove(e) {
255
- this._lastX = e.clientX;
256
- this._lastY = e.clientY;
257
- this._hoverIntentData.lastMoveTime = Date.now();
258
- this._hoverIntentData.x = e.clientX;
259
- this._hoverIntentData.y = e.clientY;
260
- }
261
-
262
- /**
263
- * 计算并设置浮层位置。
264
- * @private
265
- * @returns {void}
266
- */
267
- _setPosition() {
268
- const rect = this.target.getBoundingClientRect();
269
- const drop = this.root;
270
- const { offset, position } = this.props;
271
-
272
- drop.style.visibility = 'hidden';
273
- drop.style.display = 'block';
274
- const dropRect = drop.getBoundingClientRect();
275
- let top = 0;
276
- let left = 0;
277
-
278
- // auto 模式优先尝试上方居中,不够空间时放到下方居中。
279
- let pos = position;
280
- if (pos === 'auto') {
281
- const spaceBelow = window.innerHeight - rect.bottom;
282
- pos =
283
- spaceBelow > dropRect.height + offset ? 'top-center' : 'bottom-center';
284
- }
285
-
286
- switch (pos) {
287
- case 'top-left':
288
- top = rect.top - dropRect.height - offset;
289
- left = rect.left;
290
- break;
291
- case 'top-center':
292
- top = rect.top - dropRect.height - offset;
293
- left = rect.left + rect.width / 2 - dropRect.width / 2;
294
- break;
295
- case 'top-right':
296
- top = rect.top - dropRect.height - offset;
297
- left = rect.right - dropRect.width;
298
- break;
299
- case 'bottom-left':
300
- top = rect.bottom + offset;
301
- left = rect.left;
302
- break;
303
- case 'bottom-center':
304
- top = rect.bottom + offset;
305
- left = rect.left + rect.width / 2 - dropRect.width / 2;
306
- break;
307
- case 'bottom-right':
308
- top = rect.bottom + offset;
309
- left = rect.right - dropRect.width;
310
- break;
311
- case 'left':
312
- top = rect.top + rect.height / 2 - dropRect.height / 2;
313
- left = rect.left - dropRect.width - offset;
314
- break;
315
- case 'right':
316
- top = rect.top + rect.height / 2 - dropRect.height / 2;
317
- left = rect.right + offset;
318
- break;
319
- default:
320
- break;
321
- }
322
-
323
- // 限制在视口内。
324
- top = Math.max(8, Math.min(top, window.innerHeight - dropRect.height - 8));
325
- left = Math.max(8, Math.min(left, window.innerWidth - dropRect.width - 8));
326
-
327
- drop.style.top = `${top + window.scrollY}px`;
328
- drop.style.left = `${left + window.scrollX}px`;
329
- drop.style.visibility = '';
330
- drop.style.display = '';
331
- }
332
-
333
- _docClick(e) {
334
- if (!this.root.contains(e.target) && !this.target.contains(e.target)) {
335
- this.hide();
336
- }
337
- }
338
-
339
- /**
340
- * 应用展示或隐藏状态。
341
- * @private
342
- */
343
- _exec(visible) {
344
- if (!this.root) return;
345
-
346
- if (visible) {
347
- if (!this.root.parentNode) document.body.appendChild(this.root);
348
- this._bindRootEvents();
349
- this._setPosition();
350
- } else {
351
- this._unbindRootEvents();
352
- this.root.style.top = '';
353
- this.root.style.left = '';
354
- if (this.root.parentNode) this.root.parentNode.removeChild(this.root);
355
- }
356
-
357
- this.root.classList.toggle('is-active', visible);
358
- this.isVisible = visible;
359
- }
360
-
361
- // ========== 公开 API ==========
362
-
363
- /**
364
- * 展示浮层。
365
- * @param {boolean} [useDelay=true] 是否应用展示延迟。
366
- * @returns {void}
367
- */
368
- show(useDelay = true) {
369
- clearTimeout(this._timer.hide);
370
- if (this.isVisible) return;
371
-
372
- if (useDelay && this.delayShow > 0) {
373
- clearTimeout(this._timer.show);
374
- this._timer.show = setTimeout(() => this._exec(true), this.delayShow);
375
- } else {
376
- this._exec(true);
377
- }
378
-
379
- if (this.props.onShown) this.props.onShown();
380
- }
381
-
382
- /**
383
- * 隐藏浮层。
384
- * @param {boolean} [useDelay=true] 是否应用隐藏延迟。
385
- * @returns {void}
386
- */
387
- hide(useDelay = true) {
388
- clearTimeout(this._timer.show);
389
- if (!this.isVisible) return;
390
-
391
- if (useDelay && this.delayHide > 0) {
392
- clearTimeout(this._timer.hide);
393
- this._timer.hide = setTimeout(() => this._exec(false), this.delayHide);
394
- } else {
395
- this._exec(false);
396
- }
397
-
398
- if (this.props.onHidden) this.props.onHidden();
399
- }
400
-
401
- /**
402
- * 切换浮层显示状态。
403
- * @returns {void}
404
- */
405
- toggle() {
406
- if (this.isVisible) {
407
- this.hide();
408
- } else {
409
- this.show();
410
- }
411
- }
412
-
413
- /**
414
- * 销毁当前浮层实例并移除 DOM。
415
- * @returns {void}
416
- */
417
- destroy() {
418
- if (!this.props) return;
419
-
420
- clearTimeout(this._timer?.show);
421
- clearTimeout(this._timer?.hide);
422
-
423
- this._unbindEvents();
424
- if (this.root?.parentNode) this.root.parentNode.removeChild(this.root);
425
-
426
- this.props = null;
427
- this.root = null;
428
- this.target = null;
429
- this._timer = { show: null, hide: null };
430
- this.cleanup?.events.clear();
431
- this.cleanup = null;
432
- }
433
- }
434
-
435
- export function createDrop(container, input = {}) {
436
- return new Drop(container, input);
437
- }