vanilla-jui 1.3.9 → 1.3.10
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/dist/index.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +3 -2
- package/src/components/accordion.js +373 -0
- package/src/components/components.js +0 -0
- package/src/components/drop.js +437 -0
- package/src/components/flow.js +1133 -0
- package/src/components/form.js +585 -0
- package/src/components/icons.js +83 -0
- package/src/components/menu.js +422 -0
- package/src/components/modal.js +1174 -0
- package/src/components/offcanvas.js +332 -0
- package/src/components/pagination.js +536 -0
- package/src/components/parabola.js +295 -0
- package/src/components/sticky.js +271 -0
- package/src/components/swiper.js +1123 -0
- package/src/components/tabs.js +746 -0
- package/src/components/theme.js +353 -0
- package/src/components/toast.js +329 -0
- package/src/components/toc.js +226 -0
- package/src/components/tooltip.js +86 -0
- package/src/components/validator.js +586 -0
- package/src/core/Component.js +248 -0
- package/src/css/accordion.css +72 -0
- package/src/css/animation.css +177 -0
- package/src/css/avatar.css +62 -0
- package/src/css/badge.css +66 -0
- package/src/css/breadcrumb.css +106 -0
- package/src/css/button.css +296 -0
- package/src/css/card.css +151 -0
- package/src/css/color.css +1176 -0
- package/src/css/color_p3.css +1184 -0
- package/src/css/components.css +26 -0
- package/src/css/content.css +716 -0
- package/src/css/divider.css +9 -0
- package/src/css/drop.css +28 -0
- package/src/css/flow.css +86 -0
- package/src/css/font.css +23 -0
- package/src/css/form.css +919 -0
- package/src/css/gradient.css +211 -0
- package/src/css/icon.css +63 -0
- package/src/css/index.css +12 -0
- package/src/css/layout.css +782 -0
- package/src/css/loading.css +38 -0
- package/src/css/menu.css +564 -0
- package/src/css/modal.css +111 -0
- package/src/css/offcanvas.css +104 -0
- package/src/css/pagination.css +25 -0
- package/src/css/popup.css +69 -0
- package/src/css/skeleton.css +76 -0
- package/src/css/status.css +56 -0
- package/src/css/swiper.css +160 -0
- package/src/css/table.css +52 -0
- package/src/css/tabs.css +95 -0
- package/src/css/tag.css +98 -0
- package/src/css/theme-palette.css +59 -0
- package/src/css/tip.css +84 -0
- package/src/css/toast.css +154 -0
- package/src/css/toast.min.css +1 -0
- package/src/css/toc.css +38 -0
- package/src/css/tokens.css +1324 -0
- package/src/css/toolbar.css +115 -0
- package/src/css/tooltip.css +19 -0
- package/src/icons/index.js +30 -0
- package/src/index.js +45 -0
- package/src/locales/index.js +45 -0
- package/src/locales/index.min.js +1 -0
- package/src/utilities/browser.js +111 -0
- package/src/utilities/cache.js +159 -0
- package/src/utilities/core.js +502 -0
- package/src/utilities/debug.js +103 -0
- package/src/utilities/dom.js +313 -0
- package/src/utilities/events.js +69 -0
- package/src/utilities/http.js +30 -0
- package/src/utilities/legacy-dom.js +179 -0
- package/src/utilities/storage.js +46 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.j-toolbar-container {
|
|
3
|
+
position: var(--toolbar-container-position, sticky);
|
|
4
|
+
top: 0;
|
|
5
|
+
width: 100%;
|
|
6
|
+
z-index: var(--z-index-toolbar);
|
|
7
|
+
overflow: hidden;
|
|
8
|
+
overflow-x: auto;
|
|
9
|
+
-webkit-overflow-scrolling: touch;
|
|
10
|
+
scrollbar-color: transparent transparent;
|
|
11
|
+
scrollbar-width: none;
|
|
12
|
+
-ms-overflow-style: none;
|
|
13
|
+
scroll-behavior: smooth;
|
|
14
|
+
background: var(--toolbar-container-background, var(--ui-bg));
|
|
15
|
+
border-bottom: var(--toolbar-container-border, 1px solid var(--ui-border));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.j-toolbar {
|
|
19
|
+
position: relative;
|
|
20
|
+
/* width: 100%; */
|
|
21
|
+
display: flex;
|
|
22
|
+
justify-content: center;
|
|
23
|
+
align-items: center;
|
|
24
|
+
gap: var(--toolbar-gap, 0);
|
|
25
|
+
padding: var(--toolbar-padding, calc(var(--space) * 2));
|
|
26
|
+
box-shadow: var(--toolbar-box-shadow, var(--shadow-sm));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@media screen and (max-width: 992px) {
|
|
30
|
+
.j-toolbar {
|
|
31
|
+
justify-content: flex-start;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.j-toolbar button svg {
|
|
36
|
+
width: var(--toolbar-icon-size, var(--font-size-xl));
|
|
37
|
+
height: var(--toolbar-icon-size, var(--font-size-xl));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.j-toolbar button.is-sm svg {
|
|
41
|
+
--toolbar-icon-size: var(--font-size-lg);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.j-toolbar button.is-lg svg {
|
|
45
|
+
--toolbar-icon-size: var(--font-size-2xl);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.j-toolbar .divider {
|
|
49
|
+
width: 1px;
|
|
50
|
+
height: 16px;
|
|
51
|
+
background-color: var(--ui-fg);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* 编辑器区域 */
|
|
55
|
+
#editor {
|
|
56
|
+
max-width: 880px;
|
|
57
|
+
border-left: 1px solid var(--ui-border);
|
|
58
|
+
border-right: 1px solid var(--ui-border);
|
|
59
|
+
min-height: calc(100vh - 51px);
|
|
60
|
+
padding: 1.5rem 1.5rem 4rem;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.ProseMirror {
|
|
64
|
+
outline: 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.heading-drop-menu,
|
|
68
|
+
.editor-size-menu,
|
|
69
|
+
.color-drop-menu {
|
|
70
|
+
background: var(--ui-bg-subtle);
|
|
71
|
+
padding: var(--space);
|
|
72
|
+
box-shadow: var(--shadow-md);
|
|
73
|
+
border: 1px solid var(--ui-border);
|
|
74
|
+
border-radius: var(--radius-md);
|
|
75
|
+
font-size: var(--font-size-md)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.heading-drop-menu a,
|
|
79
|
+
.editor-size-menu a {
|
|
80
|
+
padding-block: calc(var(--space) * 1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.color-drop-menu .color-panel {
|
|
84
|
+
padding: .5rem;
|
|
85
|
+
display: grid;
|
|
86
|
+
grid-template-columns: repeat(5, 1fr);
|
|
87
|
+
gap: .5rem;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.color-drop-menu .color-panel button.color-btn {
|
|
91
|
+
padding: 0;
|
|
92
|
+
margin: 0;
|
|
93
|
+
outline: none;
|
|
94
|
+
width: 32px;
|
|
95
|
+
height: 32px;
|
|
96
|
+
border-radius: var(--radius-sm);
|
|
97
|
+
border: 1px solid var(--ui-border);
|
|
98
|
+
cursor: pointer;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.color-drop-menu .color-panel button.color-btn.color-btn-clear::after {
|
|
102
|
+
content: '🚫';
|
|
103
|
+
display: flex;
|
|
104
|
+
justify-content: center;
|
|
105
|
+
align-items: center;
|
|
106
|
+
height: 30px;
|
|
107
|
+
background-color: var(--ui-bg-subtle);
|
|
108
|
+
border-radius: var(--radius-sm);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.color-drop-menu .color-panel .color-btn:last-of-type {
|
|
112
|
+
grid-column: span 5;
|
|
113
|
+
width: auto;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
@layer comopnents {
|
|
2
|
+
.j-tooltip {
|
|
3
|
+
position: relative;
|
|
4
|
+
background: var(--tooltip-background, #000);
|
|
5
|
+
color: var(--tooltip-foreground, #fff);
|
|
6
|
+
border-width: var(--tooltip-border-width, 1px);
|
|
7
|
+
border-style: var(--tooltip-border-style, solid);
|
|
8
|
+
border-color: var(--tooltip-border-color, #111);
|
|
9
|
+
border-radius: var(--tooltip-radius, var(--radius-md));
|
|
10
|
+
max-width: var(--tooltip-max-width, 260px);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.j-tooltip .tooltip-message {
|
|
14
|
+
word-wrap: break-word;
|
|
15
|
+
padding: var(--tooltip-padding, calc(var(--space) * 1.5) calc(var(--space) * 2));
|
|
16
|
+
font-size: var(--tooltip-font-size, var(--font-size-md));
|
|
17
|
+
line-height: var(--tooltip-line-height, var(--line-height));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
info: '<path d="M12 22C6.47715 22 2 17.5228 2 12 2 6.47715 6.47715 2 12 2 17.5228 2 22 6.47715 22 12 22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM13 10.5V15H14V17H10V15H11V12.5H10V10.5H13ZM13.5 8C13.5 8.82843 12.8284 9.5 12 9.5 11.1716 9.5 10.5 8.82843 10.5 8 10.5 7.17157 11.1716 6.5 12 6.5 12.8284 6.5 13.5 7.17157 13.5 8Z"></path>',
|
|
3
|
+
success:
|
|
4
|
+
'<path d="M4 12C4 7.58172 7.58172 4 12 4C16.4183 4 20 7.58172 20 12C20 16.4183 16.4183 20 12 20C7.58172 20 4 16.4183 4 12ZM12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2ZM17.4571 9.45711L16.0429 8.04289L11 13.0858L8.20711 10.2929L6.79289 11.7071L11 15.9142L17.4571 9.45711Z"></path>',
|
|
5
|
+
warning:
|
|
6
|
+
'<path d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM11 15H13V17H11V15ZM11 7H13V13H11V7Z"></path>',
|
|
7
|
+
error:
|
|
8
|
+
'<path d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM12 10.5858L14.8284 7.75736L16.2426 9.17157L13.4142 12L16.2426 14.8284L14.8284 16.2426L12 13.4142L9.17157 16.2426L7.75736 14.8284L10.5858 12L7.75736 9.17157L9.17157 7.75736L12 10.5858Z"></path>',
|
|
9
|
+
'arrow-left':
|
|
10
|
+
'<path d="M10.8284 12.0007L15.7782 16.9504L14.364 18.3646L8 12.0007L14.364 5.63672L15.7782 7.05093L10.8284 12.0007Z"></path>',
|
|
11
|
+
'arrow-right':
|
|
12
|
+
'<path d="M13.1717 12.0007L8.22192 7.05093L9.63614 5.63672L16.0001 12.0007L9.63614 18.3646L8.22192 16.9504L13.1717 12.0007Z"></path>',
|
|
13
|
+
'arrow-up':
|
|
14
|
+
'<path d="M11.9999 10.8284L7.0502 15.7782L5.63599 14.364L11.9999 8L18.3639 14.364L16.9497 15.7782L11.9999 10.8284Z"></path>',
|
|
15
|
+
'arrow-down':
|
|
16
|
+
'<path d="M11.9999 13.1714L16.9497 8.22168L18.3639 9.63589L11.9999 15.9999L5.63599 9.63589L7.0502 8.22168L11.9999 13.1714Z"></path>',
|
|
17
|
+
more: '<path d="M5 10C3.9 10 3 10.9 3 12C3 13.1 3.9 14 5 14C6.1 14 7 13.1 7 12C7 10.9 6.1 10 5 10ZM19 10C17.9 10 17 10.9 17 12C17 13.1 17.9 14 19 14C20.1 14 21 13.1 21 12C21 10.9 20.1 10 19 10ZM12 10C10.9 10 10 10.9 10 12C10 13.1 10.9 14 12 14C13.1 14 14 13.1 14 12C14 10.9 13.1 10 12 10Z"></path>',
|
|
18
|
+
close:
|
|
19
|
+
'<path d="M11.9997 10.5865L16.9495 5.63672L18.3637 7.05093L13.4139 12.0007L18.3637 16.9504L16.9495 18.3646L11.9997 13.4149L7.04996 18.3646L5.63574 16.9504L10.5855 12.0007L5.63574 7.05093L7.04996 5.63672L11.9997 10.5865Z"></path>',
|
|
20
|
+
loader:
|
|
21
|
+
'<path d="M12 3C16.9706 3 21 7.02944 21 12H19C19 8.13401 15.866 5 12 5V3Z"></path>',
|
|
22
|
+
menu: '<path d="M3 4H21V6H3V4ZM3 11H21V13H3V11ZM3 18H21V20H3V18Z"></path>',
|
|
23
|
+
palette:
|
|
24
|
+
'<path fill="none" d="M0 0h24v24H0z"></path><path d="M12 2C17.5222 2 22 5.97778 22 10.8889C22 13.9556 19.5111 16.4444 16.4444 16.4444H14.4778C13.5556 16.4444 12.8111 17.1889 12.8111 18.1111C12.8111 18.5333 12.9778 18.9222 13.2333 19.2111C13.5 19.5111 13.6667 19.9 13.6667 20.3333C13.6667 21.2556 12.9 22 12 22C6.47778 22 2 17.5222 2 12C2 6.47778 6.47778 2 12 2ZM10.8111 18.1111C10.8111 16.0843 12.451 14.4444 14.4778 14.4444H16.4444C18.4065 14.4444 20 12.851 20 10.8889C20 7.1392 16.4677 4 12 4C7.58235 4 4 7.58235 4 12C4 16.19 7.2226 19.6285 11.324 19.9718C10.9948 19.4168 10.8111 18.7761 10.8111 18.1111ZM7.5 12C6.67157 12 6 11.3284 6 10.5C6 9.67157 6.67157 9 7.5 9C8.32843 9 9 9.67157 9 10.5C9 11.3284 8.32843 12 7.5 12ZM16.5 12C15.6716 12 15 11.3284 15 10.5C15 9.67157 15.6716 9 16.5 9C17.3284 9 18 9.67157 18 10.5C18 11.3284 17.3284 12 16.5 12ZM12 9C11.1716 9 10.5 8.32843 10.5 7.5C10.5 6.67157 11.1716 6 12 6C12.8284 6 13.5 6.67157 13.5 7.5C13.5 8.32843 12.8284 9 12 9Z"></path>',
|
|
25
|
+
message:
|
|
26
|
+
'<path d="M2 8.99374C2 5.68349 4.67654 3 8.00066 3H15.9993C19.3134 3 22 5.69478 22 8.99374V21H8.00066C4.68659 21 2 18.3052 2 15.0063V8.99374ZM20 19V8.99374C20 6.79539 18.2049 5 15.9993 5H8.00066C5.78458 5 4 6.78458 4 8.99374V15.0063C4 17.2046 5.79512 19 8.00066 19H20ZM14 11H16V13H14V11ZM8 11H10V13H8V11Z"></path>',
|
|
27
|
+
chat: '<path d="M10 3H14C18.4183 3 22 6.58172 22 11C22 15.4183 18.4183 19 14 19V22.5C9 20.5 2 17.5 2 11C2 6.58172 5.58172 3 10 3ZM12 17H14C17.3137 17 20 14.3137 20 11C20 7.68629 17.3137 5 14 5H10C6.68629 5 4 7.68629 4 11C4 14.61 6.46208 16.9656 12 19.4798V17Z"></path>',
|
|
28
|
+
discuss:
|
|
29
|
+
'<path d="M14 22.5L11.2 19H6C5.44772 19 5 18.5523 5 18V7.10256C5 6.55028 5.44772 6.10256 6 6.10256H22C22.5523 6.10256 23 6.55028 23 7.10256V18C23 18.5523 22.5523 19 22 19H16.8L14 22.5ZM15.8387 17H21V8.10256H7V17H11.2H12.1613L14 19.2984L15.8387 17ZM2 2H19V4H3V15H1V3C1 2.44772 1.44772 2 2 2Z"></path>',
|
|
30
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import './css/index.css';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Utilities
|
|
5
|
+
*/
|
|
6
|
+
export * from './utilities/browser.js';
|
|
7
|
+
export * from './utilities/cache.js';
|
|
8
|
+
export * from './utilities/core.js';
|
|
9
|
+
export * from './utilities/dom.js';
|
|
10
|
+
export * from './utilities/events.js';
|
|
11
|
+
export * from './utilities/http.js';
|
|
12
|
+
export * from './utilities/storage.js';
|
|
13
|
+
// export * from './utilities/debug.js';
|
|
14
|
+
// export * from './utilities/legacy-dom.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Icons
|
|
18
|
+
*/
|
|
19
|
+
export * from './components/icons.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Core
|
|
23
|
+
*/
|
|
24
|
+
export { default as Component } from './core/Component.js';
|
|
25
|
+
|
|
26
|
+
// components with factory functions
|
|
27
|
+
export * from './components/accordion.js';
|
|
28
|
+
export * from './components/offcanvas.js';
|
|
29
|
+
export * from './components/validator.js';
|
|
30
|
+
export * from './components/pagination.js';
|
|
31
|
+
export * from './components/drop.js';
|
|
32
|
+
export * from './components/tooltip.js';
|
|
33
|
+
export * from './components/flow.js';
|
|
34
|
+
export * from './components/form.js';
|
|
35
|
+
export * from './components/menu.js';
|
|
36
|
+
export * from './components/modal.js';
|
|
37
|
+
export * from './components/parabola.js';
|
|
38
|
+
export * from './components/sticky.js';
|
|
39
|
+
export * from './components/toc.js';
|
|
40
|
+
export * from './components/swiper.js';
|
|
41
|
+
export * from './components/tabs.js';
|
|
42
|
+
|
|
43
|
+
// components without factory functions
|
|
44
|
+
export * from './components/theme.js';
|
|
45
|
+
export * from './components/toast.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
en: {
|
|
3
|
+
b: 'Back',
|
|
4
|
+
t: 'Customize',
|
|
5
|
+
d: 'Customize your UI experience.',
|
|
6
|
+
n: 'None',
|
|
7
|
+
sm: 'Small',
|
|
8
|
+
md: 'Medium',
|
|
9
|
+
lg: 'Large',
|
|
10
|
+
},
|
|
11
|
+
zh: {
|
|
12
|
+
b: '返回',
|
|
13
|
+
t: '自定义主题',
|
|
14
|
+
d: '自定义您的UI体验。',
|
|
15
|
+
Theme: '主题',
|
|
16
|
+
Radius: '圆角',
|
|
17
|
+
Shadow: '阴影',
|
|
18
|
+
Font: '字号',
|
|
19
|
+
Mode: '模式',
|
|
20
|
+
Gray: '灰色',
|
|
21
|
+
Olive: '橄榄绿',
|
|
22
|
+
Tomato: '番茄红',
|
|
23
|
+
Ruby: '红宝石',
|
|
24
|
+
Pink: '粉红',
|
|
25
|
+
Violet: '紫罗兰',
|
|
26
|
+
Indigo: '靛蓝',
|
|
27
|
+
Blue: '蓝色',
|
|
28
|
+
Teal: '青色',
|
|
29
|
+
Grass: '草绿',
|
|
30
|
+
Mint: '薄荷绿',
|
|
31
|
+
Lime: '酸橙绿',
|
|
32
|
+
Yellow: '黄色',
|
|
33
|
+
Orange: '橙色',
|
|
34
|
+
Gold: '金色',
|
|
35
|
+
n: '无',
|
|
36
|
+
sm: '小',
|
|
37
|
+
md: '中',
|
|
38
|
+
lg: '大',
|
|
39
|
+
XL: '更大',
|
|
40
|
+
Round: '圆角',
|
|
41
|
+
Light: '浅色',
|
|
42
|
+
Dark: '深色',
|
|
43
|
+
Auto: '自动',
|
|
44
|
+
},
|
|
45
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default{en:{b:"Back",t:"Customize",d:"Customize your UI experience.",n:"None",sm:"Small",md:"Medium",lg:"Large"},zh:{b:"返回",t:"自定义主题",d:"自定义您的UI体验。",Theme:"主题",Radius:"圆角",Shadow:"阴影",Font:"字号",Mode:"模式",Gray:"灰色",Olive:"橄榄绿",Tomato:"番茄红",Ruby:"红宝石",Pink:"粉红",Violet:"紫罗兰",Indigo:"靛蓝",Blue:"蓝色",Teal:"青色",Grass:"草绿",Mint:"薄荷绿",Lime:"酸橙绿",Yellow:"黄色",Orange:"橙色",Gold:"金色",n:"无",sm:"小",md:"中",lg:"大",XL:"更大",Round:"圆角",Light:"浅色",Dark:"深色",Auto:"自动"}};
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
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
|
+
};
|