vanilla-jui 1.3.11 → 1.4.1

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 (106) hide show
  1. package/dist/index.d.ts +2921 -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 +457 -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 +0 -45
  26. package/src/css/animation.css +31 -1
  27. package/src/css/avatar.css +7 -33
  28. package/src/css/badge.css +11 -59
  29. package/src/css/breadcrumb.css +9 -32
  30. package/src/css/button.css +23 -224
  31. package/src/css/card.css +1 -1
  32. package/src/css/color.css +863 -1013
  33. package/src/css/color_p3.css +822 -975
  34. package/src/css/components.css +1 -0
  35. package/src/css/drop.css +7 -7
  36. package/src/css/flow.css +1 -1
  37. package/src/css/form.css +1 -1
  38. package/src/css/icon.css +7 -12
  39. package/src/css/index.css +5 -2
  40. package/src/css/loading.css +36 -20
  41. package/src/css/menu.css +1 -1
  42. package/src/css/modal.css +15 -44
  43. package/src/css/offcanvas.css +14 -40
  44. package/src/css/popup.css +4 -4
  45. package/src/css/status.css +0 -2
  46. package/src/css/swiper.css +35 -50
  47. package/src/css/tabs.css +9 -31
  48. package/src/css/tag.css +16 -77
  49. package/src/css/themes/default/accordion.css +62 -0
  50. package/src/css/themes/default/avatar.css +39 -0
  51. package/src/css/themes/default/badge.css +63 -0
  52. package/src/css/themes/default/breadcrumb.css +28 -0
  53. package/src/css/themes/default/button.css +204 -0
  54. package/src/css/themes/default/drop.css +11 -0
  55. package/src/css/themes/default/index.css +16 -0
  56. package/src/css/themes/default/loading.css +23 -0
  57. package/src/css/themes/default/modal.css +40 -0
  58. package/src/css/themes/default/offcanvas.css +13 -0
  59. package/src/css/themes/default/popup.css +7 -0
  60. package/src/css/themes/default/swiper.css +35 -0
  61. package/src/css/themes/default/tabs.css +37 -0
  62. package/src/css/themes/default/tag.css +85 -0
  63. package/src/css/themes/default/tip.css +49 -0
  64. package/src/css/themes/default/toast.css +70 -0
  65. package/src/css/themes/default/tooltip.css +14 -0
  66. package/src/css/themes/index.css +1 -0
  67. package/src/css/tip.css +20 -39
  68. package/src/css/toast.css +18 -80
  69. package/src/css/tokens.css +8 -3
  70. package/src/css/tooltip.css +10 -10
  71. package/src/index.ts +37 -0
  72. package/src/locales/{index.js → index.ts} +1 -2
  73. package/src/types.d.ts +1 -0
  74. package/src/utilities/browser.ts +131 -0
  75. package/src/utilities/cache.ts +57 -0
  76. package/src/utilities/core.ts +614 -0
  77. package/src/utilities/dom.ts +468 -0
  78. package/src/utilities/events.ts +106 -0
  79. package/src/utilities/http.ts +35 -0
  80. package/src/utilities/storage.ts +46 -0
  81. package/src/components/accordion.js +0 -373
  82. package/src/components/components.js +0 -0
  83. package/src/components/drop.js +0 -437
  84. package/src/components/flow.js +0 -1133
  85. package/src/components/form.js +0 -585
  86. package/src/components/menu.js +0 -422
  87. package/src/components/offcanvas.js +0 -332
  88. package/src/components/parabola.js +0 -295
  89. package/src/components/tabs.js +0 -746
  90. package/src/components/theme.js +0 -353
  91. package/src/components/toast.js +0 -329
  92. package/src/components/toc.js +0 -226
  93. package/src/components/tooltip.js +0 -86
  94. package/src/components/validator.js +0 -586
  95. package/src/index.js +0 -45
  96. package/src/locales/index.min.js +0 -1
  97. package/src/utilities/browser.js +0 -111
  98. package/src/utilities/cache.js +0 -159
  99. package/src/utilities/core.js +0 -502
  100. package/src/utilities/debug.js +0 -103
  101. package/src/utilities/dom.js +0 -313
  102. package/src/utilities/events.js +0 -69
  103. package/src/utilities/http.js +0 -30
  104. package/src/utilities/legacy-dom.js +0 -179
  105. package/src/utilities/storage.js +0 -46
  106. /package/src/icons/{index.js → index.ts} +0 -0
@@ -1,111 +0,0 @@
1
- /**
2
- * 判断当前环境是否为移动设备。
3
- * @returns {boolean}
4
- */
5
- export function isMobile() {
6
- // 1. 优先使用现代 Client Hints API (如果浏览器支持)
7
- // 这是目前最准确、最符合隐私规范的方式
8
- if (
9
- navigator.userAgentData &&
10
- typeof navigator.userAgentData.mobile === 'boolean'
11
- ) {
12
- return navigator.userAgentData.mobile;
13
- }
14
-
15
- const ua = navigator.userAgent || navigator.vendor || window.opera;
16
-
17
- // 2. 核心 UA 正则匹配 (覆盖主流手机及移动端浏览器)
18
- // 注意:这里包含了 Mobile, iPhone, Android 等,但排除了明确标识为 Tablet 的安卓设备
19
- const mobileUaRegex =
20
- /Android|iPhone|iPad|iPod|Mobile|Windows Phone|webOS|BlackBerry|IEMobile|Opera Mini/i;
21
- const isMobileUa = mobileUaRegex.test(ua);
22
-
23
- // 3. 排除平板设备 (Tablet)
24
- // 安卓平板通常包含 "Android" 但不包含 "Mobile",或者明确包含 "Tablet"
25
- const isTabletUa = /iPad|Android(?!.*Mobile)|Tablet/i.test(ua);
26
-
27
- // 4. 触摸能力检测 (作为兜底和验证)
28
- // 现代 PC 也可能有触摸屏,因此触摸仅作为辅助验证,不能单独作为判定标准
29
- const hasTouchSupport =
30
- 'maxTouchPoints' in navigator
31
- ? navigator.maxTouchPoints > 0
32
- : 'ontouchstart' in window;
33
-
34
- // 5. 屏幕尺寸检测 (CSS 像素)
35
- // 取宽高中较小的一边,防止横屏状态下宽度超过常规手机阈值
36
- const screenWidth = Math.min(window.screen.width, window.screen.height);
37
- const isSmallScreen = screenWidth <= 768;
38
-
39
- // 6. 综合判定逻辑
40
- // 必须是移动端 UA,且不是平板,并且具备小屏幕或触摸特征
41
- // 这样能有效防止桌面浏览器修改 UA 导致的误判,以及 iPad 伪装成 Mac 的问题
42
- if (isMobileUa && !isTabletUa) {
43
- // 对于 iOS 设备,UA 匹配通常足够;对于 Android,需要屏幕尺寸辅助确认
44
- if (
45
- /iPhone|iPod|Windows Phone|webOS|BlackBerry|IEMobile|Opera Mini/i.test(ua)
46
- ) {
47
- return true;
48
- }
49
- // Android 设备需结合屏幕尺寸或触摸能力,避免大屏安卓平板被误判
50
- if (/Android/i.test(ua)) {
51
- return isSmallScreen || hasTouchSupport;
52
- }
53
- }
54
-
55
- // 7. 针对 iOS 13+ iPad 伪装成 Mac 的特殊兜底
56
- // 如果 UA 显示为 Mac,但支持多点触控且屏幕较小,极大概率是 iPad
57
- const isLikelyIPad =
58
- /Macintosh|MacIntel/i.test(ua) && hasTouchSupport && isSmallScreen;
59
-
60
- return isLikelyIPad;
61
- }
62
-
63
- /**
64
- * 复制文本到剪贴板。
65
- *
66
- * 优先使用 Clipboard API,不可用时降级到 textarea + execCommand。
67
- * @param {*} text 需要复制的文本。
68
- * @returns {Promise<boolean>} 是否复制成功。
69
- */
70
- export async function copy(text) {
71
- if (typeof window === 'undefined' || typeof document === 'undefined') {
72
- return Promise.resolve(false);
73
- }
74
-
75
- const value = typeof text === 'string' ? text : String(text);
76
-
77
- if (
78
- navigator.clipboard &&
79
- typeof navigator.clipboard.writeText === 'function'
80
- ) {
81
- return navigator.clipboard
82
- .writeText(value)
83
- .then(() => true)
84
- .catch(() => fallbackCopy(value));
85
- }
86
-
87
- return Promise.resolve(fallbackCopy(value));
88
- }
89
-
90
- function fallbackCopy(text) {
91
- let textarea = null;
92
-
93
- try {
94
- textarea = document.createElement('textarea');
95
- textarea.value = text;
96
- textarea.setAttribute('readonly', '');
97
- textarea.style.position = 'absolute';
98
- textarea.style.left = '-9999px';
99
- textarea.style.opacity = '0';
100
- document.body.appendChild(textarea);
101
- textarea.select();
102
- textarea.setSelectionRange(0, text.length);
103
- return !!document.execCommand('copy');
104
- } catch {
105
- return false;
106
- } finally {
107
- if (textarea && textarea.parentNode) {
108
- textarea.parentNode.removeChild(textarea);
109
- }
110
- }
111
- }
@@ -1,159 +0,0 @@
1
- import { isFunction, randomId } from './core.js';
2
-
3
- const instances = new Map();
4
- const timers = new Map();
5
-
6
- /**
7
- * 创建或获取带缓存的类实例。
8
- *
9
- * 支持按 cacheKey 缓存实例,并可通过 ttl 设置空闲释放时间。
10
- * @template T
11
- * @param {new (...args:any[])=>T} Class 构造函数。
12
- * @param {any[]} [args=[]] 构造参数。
13
- * @param {string} [cacheKey=""] 缓存键;为空时自动生成。
14
- * @param {boolean} [isReset=false] 是否忽略已有缓存并重新创建。
15
- * @param {number|null} [ttl=null] 空闲释放时间,单位毫秒。
16
- * @returns {T} 实例代理。
17
- */
18
- export const singleton = (
19
- Class,
20
- args = [],
21
- cacheKey = '',
22
- isReset = false,
23
- ttl = null
24
- ) => {
25
- if (!isFunction(Class)) {
26
- throw new Error('First argument expects a class/constructor function.');
27
- }
28
- if (!Array.isArray(args)) {
29
- throw new Error('Second argument must be an array of arguments.');
30
- }
31
- if (typeof cacheKey !== 'string') {
32
- throw new Error('Third argument expects a string.');
33
- }
34
- if (typeof isReset !== 'boolean') {
35
- throw new Error('Fourth argument expects a boolean.');
36
- }
37
- if (typeof ttl !== 'number' && ttl !== null) {
38
- throw new Error('Fifth argument expects a number or null.');
39
- }
40
-
41
- const key = cacheKey.trim() === '' ? randomId() : cacheKey;
42
-
43
- if (instances.has(key) && !isReset) {
44
- return instances.get(key).proxy;
45
- }
46
-
47
- const instance = new Class(...args);
48
- const proxy = new Proxy(instance, {
49
- get(target, name, receiver) {
50
- if (ttl) refreshTimer(key, ttl);
51
- return Reflect.get(target, name, receiver);
52
- },
53
- set(target, name, value, receiver) {
54
- return Reflect.set(target, name, value, receiver);
55
- },
56
- });
57
-
58
- instances.set(key, { instance, proxy });
59
- return proxy;
60
- };
61
-
62
- function refreshTimer(key, ttl) {
63
- if (timers.has(key)) {
64
- clearTimeout(timers.get(key));
65
- }
66
-
67
- const timeId = setTimeout(() => {
68
- release(key);
69
- }, ttl);
70
-
71
- timers.set(key, timeId);
72
- }
73
-
74
- /**
75
- * 释放指定缓存实例。
76
- *
77
- * 若实例存在 destroy 方法,会先调用 destroy。
78
- * @param {string} key 缓存键。
79
- * @returns {void}
80
- */
81
- export function release(key) {
82
- if (instances.has(key)) {
83
- const { instance } = instances.get(key);
84
-
85
- if (typeof instance.destroy === 'function') {
86
- try {
87
- instance.destroy();
88
- } catch {
89
- throw new Error(
90
- `release(): destroy() failed on instance with key "${key}".`
91
- );
92
- }
93
- }
94
-
95
- instances.delete(key);
96
- }
97
-
98
- if (timers.has(key)) {
99
- clearTimeout(timers.get(key));
100
- timers.delete(key);
101
- }
102
- }
103
-
104
- /**
105
- * 释放 singleton 管理的全部实例。
106
- * @returns {void}
107
- */
108
- export function destroyAll() {
109
- for (const { instance } of instances.values()) {
110
- if (typeof instance.destroy === 'function') {
111
- try {
112
- instance.destroy();
113
- } catch {}
114
- }
115
- }
116
-
117
- instances.clear();
118
-
119
- for (const timerId of timers.values()) {
120
- clearTimeout(timerId);
121
- }
122
-
123
- timers.clear();
124
- }
125
-
126
- /**
127
- * 简易服务缓存容器。
128
- *
129
- * 用于按 key 缓存工厂创建的服务实例,并提供销毁能力。
130
- */
131
- export const service = {
132
- instances: new Map(),
133
-
134
- get(key, factory) {
135
- if (!this.instances.has(key)) {
136
- this.instances.set(key, factory());
137
- }
138
- return this.instances.get(key);
139
- },
140
-
141
- destroy(key) {
142
- if (this.instances.has(key)) {
143
- const instance = this.instances.get(key);
144
- if (instance.destroy) {
145
- instance.destroy();
146
- }
147
- this.instances.delete(key);
148
- }
149
- },
150
-
151
- destroyAll() {
152
- for (const instance of this.instances.values()) {
153
- if (instance.destroy) {
154
- instance.destroy();
155
- }
156
- }
157
- this.instances.clear();
158
- },
159
- };