vanilla-jui 1.4.5 → 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.
- package/README.md +6 -0
- package/dist/index.d.ts +1273 -1896
- package/dist/index.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +21 -2
- package/src/components/accordion.ts +297 -216
- package/src/components/flow.ts +222 -258
- package/src/components/form.ts +44 -102
- package/src/components/menu.ts +268 -356
- package/src/components/modal.ts +181 -641
- package/src/components/offcanvas.ts +225 -106
- package/src/components/pagination.ts +296 -326
- package/src/components/sticky.ts +55 -54
- package/src/components/swiper.ts +237 -247
- package/src/components/tabs.ts +278 -271
- package/src/components/toc.ts +42 -46
- package/src/core/Component.ts +69 -47
- package/src/css/accordion.css +2 -2
- package/src/css/animation.css +1 -5
- package/src/css/avatar.css +1 -1
- package/src/css/badge.css +1 -1
- package/src/css/breadcrumb.css +9 -10
- package/src/css/button.css +1 -1
- package/src/css/card.css +2 -2
- package/src/css/color.css +974 -1002
- package/src/css/color_p3.css +974 -1004
- package/src/css/components.css +1 -1
- package/src/css/content.css +72 -63
- package/src/css/drop.css +1 -1
- package/src/css/editor.css +14 -14
- package/src/css/flow.css +4 -14
- package/src/css/font.css +5 -4
- package/src/css/form.css +76 -52
- package/src/css/gradient.css +299 -222
- package/src/css/icon.css +57 -57
- package/src/css/index.css +1 -1
- package/src/css/layout.css +13 -10
- package/src/css/loading.css +60 -12
- package/src/css/menu.css +262 -188
- package/src/css/modal.css +1 -1
- package/src/css/offcanvas.css +9 -13
- package/src/css/pagination.css +5 -1
- package/src/css/popup.css +1 -1
- package/src/css/skeleton.css +3 -2
- package/src/css/status.css +2 -3
- package/src/css/swiper.css +1 -1
- package/src/css/table.css +1 -1
- package/src/css/tabs.css +12 -11
- package/src/css/tag.css +1 -1
- package/src/css/theme-palette.css +19 -5
- package/src/css/themes/default/accordion.css +61 -61
- package/src/css/themes/default/avatar.css +54 -54
- package/src/css/themes/default/badge.css +62 -62
- package/src/css/themes/default/breadcrumb.css +23 -23
- package/src/css/themes/default/button.css +203 -203
- package/src/css/themes/default/content.css +53 -53
- package/src/css/themes/default/drop.css +10 -10
- package/src/css/themes/default/editor.css +9 -9
- package/src/css/themes/default/index.css +1 -1
- package/src/css/themes/default/loading.css +26 -20
- package/src/css/themes/default/menu.css +75 -11
- package/src/css/themes/default/modal.css +32 -36
- package/src/css/themes/default/pagination.css +7 -7
- package/src/css/themes/default/popup.css +6 -6
- package/src/css/themes/default/swiper.css +32 -32
- package/src/css/themes/default/table.css +16 -16
- package/src/css/themes/default/tabs.css +30 -30
- package/src/css/themes/default/tag.css +71 -71
- package/src/css/themes/default/theme-palette.css +11 -11
- package/src/css/themes/default/tip.css +42 -42
- package/src/css/themes/default/toast.css +60 -60
- package/src/css/themes/default/toolbar.css +23 -29
- package/src/css/themes/default/tooltip.css +43 -13
- package/src/css/themes/index.css +1 -1
- package/src/css/tip.css +2 -2
- package/src/css/toast.css +3 -3
- package/src/css/tokens.css +12 -17
- package/src/css/toolbar.css +9 -10
- package/src/css/tooltip.css +3 -2
- package/src/index.ts +22 -19
- package/src/{components → primitives}/drop.ts +58 -49
- package/src/{components → primitives}/icons.ts +1 -1
- package/src/primitives/loading.ts +23 -0
- package/src/primitives/parabola.ts +388 -0
- package/src/primitives/popup.ts +61 -0
- package/src/{components → primitives}/theme.ts +88 -57
- package/src/{components → primitives}/toast.ts +18 -25
- package/src/{components → primitives}/tooltip.ts +100 -25
- package/src/utilities/browser.ts +23 -49
- package/src/utilities/class-name.ts +5 -0
- package/src/utilities/dom.ts +14 -32
- package/src/utilities/function.ts +4 -0
- package/src/utilities/http.ts +7 -7
- package/src/utilities/id.ts +57 -0
- package/src/utilities/object.ts +5 -0
- package/src/utilities/timer.ts +38 -0
- package/src/utilities/types.ts +420 -0
- package/src/{components → validation}/validator.ts +64 -53
- package/src/components/parabola.ts +0 -335
- package/src/utilities/cache.ts +0 -57
- package/src/utilities/core.ts +0 -614
- package/src/utilities/storage.ts +0 -46
package/src/utilities/browser.ts
CHANGED
|
@@ -4,8 +4,11 @@ declare global {
|
|
|
4
4
|
mobile: boolean;
|
|
5
5
|
brands: Array<{ brand: string; version: string }>;
|
|
6
6
|
platform: string;
|
|
7
|
-
getHighEntropyValues: (
|
|
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
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
22
|
+
if (typeof navigator === 'undefined') return false;
|
|
23
23
|
|
|
24
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
44
|
+
return (
|
|
45
|
+
window.matchMedia('(pointer: coarse)').matches &&
|
|
46
|
+
window.matchMedia('(max-width: 820px)').matches
|
|
47
|
+
);
|
|
74
48
|
}
|
|
75
49
|
|
|
76
50
|
/**
|
package/src/utilities/dom.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
return
|
|
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
|
/**
|
package/src/utilities/http.ts
CHANGED
|
@@ -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,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
|
+
};
|