vanilla-jui 1.4.4 → 1.4.6

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 (103) hide show
  1. package/README.md +6 -0
  2. package/dist/index.d.ts +1273 -1896
  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 +22 -3
  7. package/src/components/accordion.ts +297 -216
  8. package/src/components/flow.ts +222 -258
  9. package/src/components/form.ts +44 -102
  10. package/src/components/menu.ts +268 -356
  11. package/src/components/modal.ts +181 -641
  12. package/src/components/offcanvas.ts +225 -106
  13. package/src/components/pagination.ts +296 -326
  14. package/src/components/sticky.ts +55 -54
  15. package/src/components/swiper.ts +237 -247
  16. package/src/components/tabs.ts +278 -271
  17. package/src/components/toc.ts +42 -46
  18. package/src/core/Component.ts +69 -47
  19. package/src/css/accordion.css +2 -2
  20. package/src/css/animation.css +1 -5
  21. package/src/css/avatar.css +1 -1
  22. package/src/css/badge.css +1 -1
  23. package/src/css/breadcrumb.css +9 -10
  24. package/src/css/button.css +1 -1
  25. package/src/css/card.css +2 -2
  26. package/src/css/color.css +974 -1002
  27. package/src/css/color_p3.css +974 -1004
  28. package/src/css/components.css +1 -1
  29. package/src/css/content.css +72 -63
  30. package/src/css/drop.css +1 -1
  31. package/src/css/editor.css +14 -14
  32. package/src/css/flow.css +4 -14
  33. package/src/css/font.css +5 -4
  34. package/src/css/form.css +76 -52
  35. package/src/css/gradient.css +299 -222
  36. package/src/css/icon.css +57 -57
  37. package/src/css/index.css +1 -1
  38. package/src/css/layout.css +13 -10
  39. package/src/css/loading.css +60 -12
  40. package/src/css/menu.css +262 -188
  41. package/src/css/modal.css +1 -1
  42. package/src/css/offcanvas.css +9 -13
  43. package/src/css/pagination.css +5 -1
  44. package/src/css/popup.css +1 -1
  45. package/src/css/skeleton.css +3 -2
  46. package/src/css/status.css +2 -3
  47. package/src/css/swiper.css +1 -1
  48. package/src/css/table.css +1 -1
  49. package/src/css/tabs.css +12 -11
  50. package/src/css/tag.css +1 -1
  51. package/src/css/theme-palette.css +19 -5
  52. package/src/css/themes/default/accordion.css +61 -61
  53. package/src/css/themes/default/avatar.css +54 -54
  54. package/src/css/themes/default/badge.css +62 -62
  55. package/src/css/themes/default/breadcrumb.css +23 -23
  56. package/src/css/themes/default/button.css +203 -203
  57. package/src/css/themes/default/content.css +53 -53
  58. package/src/css/themes/default/drop.css +10 -10
  59. package/src/css/themes/default/editor.css +9 -9
  60. package/src/css/themes/default/index.css +1 -1
  61. package/src/css/themes/default/loading.css +26 -20
  62. package/src/css/themes/default/menu.css +75 -11
  63. package/src/css/themes/default/modal.css +32 -36
  64. package/src/css/themes/default/pagination.css +7 -7
  65. package/src/css/themes/default/popup.css +6 -6
  66. package/src/css/themes/default/swiper.css +32 -32
  67. package/src/css/themes/default/table.css +16 -16
  68. package/src/css/themes/default/tabs.css +30 -30
  69. package/src/css/themes/default/tag.css +71 -71
  70. package/src/css/themes/default/theme-palette.css +11 -11
  71. package/src/css/themes/default/tip.css +42 -42
  72. package/src/css/themes/default/toast.css +60 -60
  73. package/src/css/themes/default/toolbar.css +23 -29
  74. package/src/css/themes/default/tooltip.css +43 -13
  75. package/src/css/themes/index.css +1 -1
  76. package/src/css/tip.css +2 -2
  77. package/src/css/toast.css +3 -3
  78. package/src/css/tokens.css +12 -17
  79. package/src/css/toolbar.css +9 -10
  80. package/src/css/tooltip.css +3 -2
  81. package/src/index.ts +22 -19
  82. package/src/{components → primitives}/drop.ts +58 -49
  83. package/src/{components → primitives}/icons.ts +1 -1
  84. package/src/primitives/loading.ts +23 -0
  85. package/src/primitives/parabola.ts +388 -0
  86. package/src/primitives/popup.ts +61 -0
  87. package/src/{components → primitives}/theme.ts +88 -57
  88. package/src/{components → primitives}/toast.ts +18 -25
  89. package/src/{components → primitives}/tooltip.ts +100 -25
  90. package/src/utilities/browser.ts +23 -49
  91. package/src/utilities/class-name.ts +5 -0
  92. package/src/utilities/dom.ts +14 -32
  93. package/src/utilities/function.ts +4 -0
  94. package/src/utilities/http.ts +7 -7
  95. package/src/utilities/id.ts +57 -0
  96. package/src/utilities/object.ts +5 -0
  97. package/src/utilities/timer.ts +38 -0
  98. package/src/utilities/types.ts +420 -0
  99. package/src/{components → validation}/validator.ts +64 -53
  100. package/src/components/parabola.ts +0 -335
  101. package/src/utilities/cache.ts +0 -57
  102. package/src/utilities/core.ts +0 -614
  103. package/src/utilities/storage.ts +0 -46
@@ -4,8 +4,11 @@ declare global {
4
4
  mobile: boolean;
5
5
  brands: Array<{ brand: string; version: string }>;
6
6
  platform: string;
7
- getHighEntropyValues: (hints: string[]) => Promise<any>;
7
+ getHighEntropyValues: (
8
+ hints: string[]
9
+ ) => Promise<Record<string, unknown>>;
8
10
  };
11
+ msMaxTouchPoints?: number;
9
12
  }
10
13
  interface Window {
11
14
  opera?: string;
@@ -13,64 +16,35 @@ declare global {
13
16
  }
14
17
 
15
18
  /**
16
- * 判断当前环境是否为移动设备。
17
- * @returns {boolean}
19
+ * 采用 MDN 推荐的组合策略:特性检测优先,UA 嗅探兜底
18
20
  */
19
21
  export function isMobile(): boolean {
20
- if (typeof window === 'undefined' || typeof navigator === 'undefined') {
21
- return false;
22
- }
22
+ if (typeof navigator === 'undefined') return false;
23
23
 
24
- // 1. 优先使用现代 Client Hints API
25
- if (
26
- navigator.userAgentData &&
27
- typeof navigator.userAgentData.mobile === 'boolean'
28
- ) {
24
+ if (typeof navigator.userAgentData?.mobile === 'boolean') {
29
25
  return navigator.userAgentData.mobile;
30
26
  }
31
27
 
32
- // 提取 UA 字符串并做安全处理
33
- const ua = navigator.userAgent || navigator.vendor || window.opera || '';
34
-
35
- // 2. 核心 UA 正则匹配
36
- const mobileUaRegex =
37
- /Android|iPhone|iPad|iPod|Mobile|Windows Phone|webOS|BlackBerry|IEMobile|Opera Mini/i;
38
- const isMobileUa = mobileUaRegex.test(ua);
39
-
40
- // 3. 排除平板设备 (Tablet)
41
- const isTabletUa = /iPad|Android(?!.*Mobile)|Tablet/i.test(ua);
42
-
43
- // 4. 综合判定逻辑(针对 iOS 和 Android 分别处理)
44
- if (isMobileUa && !isTabletUa) {
45
- // 对于 iOS 设备,UA 匹配通常足够
46
- if (
47
- /iPhone|iPod|Windows Phone|webOS|BlackBerry|IEMobile|Opera Mini/i.test(ua)
48
- ) {
49
- return true;
50
- }
51
- // Android 设备需结合屏幕尺寸或触摸能力,避免大屏安卓平板被误判
52
- if (/Android/i.test(ua)) {
53
- const hasTouchSupport =
54
- 'maxTouchPoints' in navigator
55
- ? navigator.maxTouchPoints > 0
56
- : 'ontouchstart' in window;
57
- const screenWidth = Math.min(window.screen.width, window.screen.height);
58
- return screenWidth <= 768 || hasTouchSupport;
59
- }
28
+ const ua = navigator.userAgent || '';
29
+ if (
30
+ /\b(BlackBerry|webOS|iPhone|IEMobile|Android|Windows Phone|iPad|iPod)\b/i.test(
31
+ ua
32
+ )
33
+ ) {
34
+ return true;
60
35
  }
61
36
 
62
- // 5. 针对 iOS 13+ iPad 伪装成 Mac 的特殊兜底
63
- const isLikelyIPad = /Macintosh|MacIntel/i.test(ua);
64
- if (isLikelyIPad) {
65
- const hasTouchSupport =
66
- 'maxTouchPoints' in navigator
67
- ? navigator.maxTouchPoints > 0
68
- : 'ontouchstart' in window;
69
- const screenWidth = Math.min(window.screen.width, window.screen.height);
70
- return hasTouchSupport && screenWidth <= 768;
37
+ if (
38
+ typeof window === 'undefined' ||
39
+ typeof window.matchMedia !== 'function'
40
+ ) {
41
+ return false;
71
42
  }
72
43
 
73
- return false;
44
+ return (
45
+ window.matchMedia('(pointer: coarse)').matches &&
46
+ window.matchMedia('(max-width: 820px)').matches
47
+ );
74
48
  }
75
49
 
76
50
  /**
@@ -0,0 +1,5 @@
1
+ export type ClassNameToken = string | false | null | undefined;
2
+
3
+ export function joinClasses(...tokens: ClassNameToken[]): string {
4
+ return tokens.filter(Boolean).join(' ');
5
+ }
@@ -1,6 +1,8 @@
1
- import { jsx } from 'vanilla-signal';
2
-
3
- import { icon } from '../components/icons';
1
+ import {
2
+ isDomElementValue,
3
+ isDomNodeValue,
4
+ isRenderableValue,
5
+ } from './types.ts';
4
6
 
5
7
  export type ContainerExpect = 'node' | 'element' | 'array';
6
8
  export type ResolveContainerResult<
@@ -49,7 +51,7 @@ function isContainerExpect(value: unknown): value is ContainerExpect {
49
51
  * @returns {boolean}
50
52
  */
51
53
  export function isNode(value: unknown): value is Node {
52
- return value instanceof Node;
54
+ return isDomNodeValue(value);
53
55
  }
54
56
 
55
57
  /**
@@ -58,7 +60,7 @@ export function isNode(value: unknown): value is Node {
58
60
  * @returns {boolean}
59
61
  */
60
62
  export function isElement(value: unknown): value is Element {
61
- return value instanceof Element;
63
+ return isDomElementValue(value);
62
64
  }
63
65
 
64
66
  /**
@@ -294,35 +296,15 @@ export function requireContainer<TExpect extends ContainerExpect = 'element'>(
294
296
  export function isRenderableContent(
295
297
  value: unknown
296
298
  ): value is RenderableContent {
297
- return (
298
- value == null ||
299
- typeof value === 'string' ||
300
- typeof value === 'number' ||
301
- typeof value === 'boolean' ||
302
- typeof value === 'function' ||
303
- Array.isArray(value) ||
304
- isNode(value)
305
- );
299
+ return isRenderableValue(value);
306
300
  }
307
301
 
308
- /**
309
- * 创建通用加载状态节点。
310
- * @returns {HTMLElement}
311
- */
312
- export function createLoading(): HTMLDivElement {
313
- return jsx('div', {
314
- 'aria-live': 'polite',
315
- style: {
316
- display: 'flex',
317
- justifyContent: 'center',
318
- alignItems: 'center',
319
- position: 'absolute',
320
- width: '100%',
321
- height: '100%',
322
- backdropFilter: 'blur(4px)',
323
- },
324
- children: icon('loader', { width: 24, className: 'animate-spin' }),
325
- });
302
+ export function normalizeRenderableContentNodes<TContext>(
303
+ content: unknown,
304
+ context: TContext
305
+ ): Node[] | null {
306
+ if (!isRenderableContent(content)) return null;
307
+ return normalizeContentNodes(content, context);
326
308
  }
327
309
 
328
310
  /**
@@ -0,0 +1,4 @@
1
+ export { default as debounce } from 'lodash-es/debounce.js';
2
+ export { default as throttle } from 'lodash-es/throttle.js';
3
+ export type { DebouncedFunc, DebounceSettings } from 'lodash-es/debounce.js';
4
+ export type { ThrottleSettings } from 'lodash-es/throttle.js';
@@ -1,3 +1,10 @@
1
+ /**
2
+ * 当前站点 WordPress REST API 根地址。
3
+ * @type {string}
4
+ */
5
+ export const restUrl: string =
6
+ typeof window !== 'undefined' ? `${window.location.origin}/wp-json` : '';
7
+
1
8
  /**
2
9
  * 发送 JSON POST 请求并解析 JSON 响应。
3
10
  * @template T - 期望返回的 JSON 数据类型
@@ -26,10 +33,3 @@ export async function postJson<T = unknown>(
26
33
 
27
34
  return res.json() as Promise<T>;
28
35
  }
29
-
30
- /**
31
- * 当前站点 WordPress REST API 根地址。
32
- * @type {string}
33
- */
34
- export const restUrl: string =
35
- typeof window !== 'undefined' ? `${window.location.origin}/wp-json` : '';
@@ -0,0 +1,57 @@
1
+ /**
2
+ * 生成标准 UUID v4 字符串。
3
+ *
4
+ * 优先使用浏览器原生的 crypto.randomUUID(),
5
+ * 不支持时使用 polyfill 实现。
6
+ *
7
+ * @returns UUID v4 字符串(如 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
8
+ */
9
+ export function uuid(): string {
10
+ if (typeof crypto.randomUUID === 'function') {
11
+ return crypto.randomUUID();
12
+ }
13
+ const bytes = new Uint8Array(16);
14
+ if (typeof crypto.getRandomValues !== 'function') {
15
+ throw new Error('Your browser is too old to support secure login.');
16
+ }
17
+ crypto.getRandomValues(bytes);
18
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
19
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
20
+ const hex = Array.from(bytes)
21
+ .map((b) => b.toString(16).padStart(2, '0'))
22
+ .join('');
23
+ return [
24
+ hex.slice(0, 8),
25
+ hex.slice(8, 12),
26
+ hex.slice(12, 16),
27
+ hex.slice(16, 20),
28
+ hex.slice(20, 32),
29
+ ].join('-');
30
+ }
31
+
32
+ /**
33
+ * 生成适合 DOM id 的随机字符串。
34
+ *
35
+ * 使用安全的随机数生成器,生成的字符串适合作为 HTML 元素的 id 属性。
36
+ *
37
+ * @param [length=8] - 字符串长度,范围 1 到 87381
38
+ * @returns 随机字符串
39
+ * @throws {Error} 长度不在有效范围内时抛出错误
40
+ */
41
+ export function randomId(length: number = 8): string {
42
+ if (!Number.isInteger(length) || length < 1 || length > 87381) {
43
+ throw new Error('Length must be an integer between 1 and 87381');
44
+ }
45
+
46
+ const byteLength = Math.ceil((length * 3) / 4);
47
+ const bytes = new Uint8Array(byteLength);
48
+ crypto.getRandomValues(bytes);
49
+
50
+ let binary = '';
51
+ for (const byte of bytes) {
52
+ binary += String.fromCharCode(byte);
53
+ }
54
+ const base64 = btoa(binary);
55
+
56
+ return base64.substring(0, length).replace(/\+/g, '-').replace(/\//g, '_');
57
+ }
@@ -0,0 +1,5 @@
1
+ import _isPlainObject from 'lodash-es/isPlainObject.js';
2
+
3
+ export function isPlainObject(obj: unknown): boolean {
4
+ return _isPlainObject(obj);
5
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * 定时器管理器
3
+ */
4
+ export const timer = {
5
+ // 存储定时器 { key: timerId }
6
+ timers: {} as Record<string, ReturnType<typeof setTimeout>>,
7
+
8
+ /**
9
+ * 注册并开始一个定时器
10
+ * @param {string} key - 定时器的唯一标识
11
+ * @param {number} duration - 延迟执行的时间(毫秒)
12
+ * @param {function} callback - 延迟执行的回调函数
13
+ */
14
+ start(key: string, duration: number, callback: () => void) {
15
+ // 1. 如果该 key 已经存在,先清除旧的定时器,防止重复触发
16
+ if (this.timers[key]) {
17
+ clearTimeout(this.timers[key]);
18
+ }
19
+ // 2. 设置新的定时器,并保存其 ID
20
+ this.timers[key] = setTimeout(() => {
21
+ callback();
22
+ // 3. 执行完毕后,自动从管理器中注销该定时器
23
+ delete this.timers[key];
24
+ }, duration);
25
+ },
26
+
27
+ /**
28
+ * 明确注销一个定时器
29
+ * @param {string} key - 需要取消的定时器唯一标识
30
+ */
31
+ cancel(key: string) {
32
+ if (this.timers[key]) {
33
+ clearTimeout(this.timers[key]);
34
+ // 从管理器中移除该记录
35
+ delete this.timers[key];
36
+ }
37
+ },
38
+ };