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.
- package/dist/index.d.ts +2921 -0
- package/dist/index.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +4 -3
- package/src/components/accordion.ts +522 -0
- package/src/components/drop.ts +497 -0
- package/src/components/flow.ts +1579 -0
- package/src/components/form.ts +953 -0
- package/src/components/{icons.js → icons.ts} +24 -11
- package/src/components/menu.ts +615 -0
- package/src/components/{modal.js → modal.ts} +560 -237
- package/src/components/offcanvas.ts +457 -0
- package/src/components/{pagination.js → pagination.ts} +251 -76
- package/src/components/parabola.ts +335 -0
- package/src/components/{sticky.js → sticky.ts} +123 -98
- package/src/components/{swiper.js → swiper.ts} +510 -232
- package/src/components/tabs.ts +984 -0
- package/src/components/theme.ts +437 -0
- package/src/components/toast.ts +371 -0
- package/src/components/toc.ts +385 -0
- package/src/components/tooltip.ts +179 -0
- package/src/components/validator.ts +596 -0
- package/src/core/{Component.js → Component.ts} +134 -27
- package/src/css/accordion.css +0 -45
- package/src/css/animation.css +31 -1
- package/src/css/avatar.css +7 -33
- package/src/css/badge.css +11 -59
- package/src/css/breadcrumb.css +9 -32
- package/src/css/button.css +23 -224
- package/src/css/card.css +1 -1
- package/src/css/color.css +863 -1013
- package/src/css/color_p3.css +822 -975
- package/src/css/components.css +1 -0
- package/src/css/drop.css +7 -7
- package/src/css/flow.css +1 -1
- package/src/css/form.css +1 -1
- package/src/css/icon.css +7 -12
- package/src/css/index.css +5 -2
- package/src/css/loading.css +36 -20
- package/src/css/menu.css +1 -1
- package/src/css/modal.css +15 -44
- package/src/css/offcanvas.css +14 -40
- package/src/css/popup.css +4 -4
- package/src/css/status.css +0 -2
- package/src/css/swiper.css +35 -50
- package/src/css/tabs.css +9 -31
- package/src/css/tag.css +16 -77
- package/src/css/themes/default/accordion.css +62 -0
- package/src/css/themes/default/avatar.css +39 -0
- package/src/css/themes/default/badge.css +63 -0
- package/src/css/themes/default/breadcrumb.css +28 -0
- package/src/css/themes/default/button.css +204 -0
- package/src/css/themes/default/drop.css +11 -0
- package/src/css/themes/default/index.css +16 -0
- package/src/css/themes/default/loading.css +23 -0
- package/src/css/themes/default/modal.css +40 -0
- package/src/css/themes/default/offcanvas.css +13 -0
- package/src/css/themes/default/popup.css +7 -0
- package/src/css/themes/default/swiper.css +35 -0
- package/src/css/themes/default/tabs.css +37 -0
- package/src/css/themes/default/tag.css +85 -0
- package/src/css/themes/default/tip.css +49 -0
- package/src/css/themes/default/toast.css +70 -0
- package/src/css/themes/default/tooltip.css +14 -0
- package/src/css/themes/index.css +1 -0
- package/src/css/tip.css +20 -39
- package/src/css/toast.css +18 -80
- package/src/css/tokens.css +8 -3
- package/src/css/tooltip.css +10 -10
- package/src/index.ts +37 -0
- package/src/locales/{index.js → index.ts} +1 -2
- package/src/types.d.ts +1 -0
- package/src/utilities/browser.ts +131 -0
- package/src/utilities/cache.ts +57 -0
- package/src/utilities/core.ts +614 -0
- package/src/utilities/dom.ts +468 -0
- package/src/utilities/events.ts +106 -0
- package/src/utilities/http.ts +35 -0
- package/src/utilities/storage.ts +46 -0
- package/src/components/accordion.js +0 -373
- package/src/components/components.js +0 -0
- package/src/components/drop.js +0 -437
- package/src/components/flow.js +0 -1133
- package/src/components/form.js +0 -585
- package/src/components/menu.js +0 -422
- package/src/components/offcanvas.js +0 -332
- package/src/components/parabola.js +0 -295
- package/src/components/tabs.js +0 -746
- package/src/components/theme.js +0 -353
- package/src/components/toast.js +0 -329
- package/src/components/toc.js +0 -226
- package/src/components/tooltip.js +0 -86
- package/src/components/validator.js +0 -586
- package/src/index.js +0 -45
- package/src/locales/index.min.js +0 -1
- package/src/utilities/browser.js +0 -111
- package/src/utilities/cache.js +0 -159
- package/src/utilities/core.js +0 -502
- package/src/utilities/debug.js +0 -103
- package/src/utilities/dom.js +0 -313
- package/src/utilities/events.js +0 -69
- package/src/utilities/http.js +0 -30
- package/src/utilities/legacy-dom.js +0 -179
- package/src/utilities/storage.js +0 -46
- /package/src/icons/{index.js → index.ts} +0 -0
package/src/utilities/dom.js
DELETED
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
import { jsx } from 'vanilla-signal';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 判断当前环境是否可访问 DOM。
|
|
5
|
-
* @returns {boolean}
|
|
6
|
-
*/
|
|
7
|
-
export function canUseDOM() {
|
|
8
|
-
return typeof document !== 'undefined';
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* 判断当前环境是否可执行 DOM 渲染。
|
|
13
|
-
* @returns {boolean}
|
|
14
|
-
*/
|
|
15
|
-
export function canRenderDOM() {
|
|
16
|
-
if (!canUseDOM() || typeof document.createElement !== 'function') {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const element = document.createElement('div');
|
|
21
|
-
return typeof element.insertBefore === 'function';
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* 强制要求当前环境可执行 DOM 渲染。
|
|
26
|
-
* @param {string} [namespace='Component'] 错误命名空间。
|
|
27
|
-
* @returns {true}
|
|
28
|
-
* @throws {Error} 当前环境不可渲染 DOM 时抛出。
|
|
29
|
-
*/
|
|
30
|
-
export function requireRenderDOM(namespace = 'Component') {
|
|
31
|
-
if (!canRenderDOM()) {
|
|
32
|
-
throw new Error(`${namespace}: DOM render environment is required.`);
|
|
33
|
-
}
|
|
34
|
-
return true;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 判断是否为 DOM Node。
|
|
39
|
-
* @param {*} value 需要判断的值。
|
|
40
|
-
* @returns {boolean}
|
|
41
|
-
*/
|
|
42
|
-
export function isNode(value) {
|
|
43
|
-
return canUseDOM() && typeof Node !== 'undefined' && value instanceof Node;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* 判断是否为 DOM Element。
|
|
48
|
-
* @param {*} value 需要判断的值。
|
|
49
|
-
* @returns {boolean}
|
|
50
|
-
*/
|
|
51
|
-
export function isElement(value) {
|
|
52
|
-
return (
|
|
53
|
-
canUseDOM() && typeof Element !== 'undefined' && value instanceof Element
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* 判断值是否为可接受的容器输入。
|
|
59
|
-
* @param {*} value 需要判断的值。
|
|
60
|
-
* @returns {boolean}
|
|
61
|
-
*/
|
|
62
|
-
function isContainerLike(value) {
|
|
63
|
-
return (
|
|
64
|
-
typeof value === 'string' ||
|
|
65
|
-
isElement(value) ||
|
|
66
|
-
isNode(value) ||
|
|
67
|
-
Array.isArray(value)
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function firstElement(nodes) {
|
|
72
|
-
return nodes.find(isElement) || null;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function flattenNodeArray(value, out = []) {
|
|
76
|
-
for (const item of value) {
|
|
77
|
-
if (Array.isArray(item)) {
|
|
78
|
-
if (!flattenNodeArray(item, out)) return null;
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (isNode(item)) {
|
|
83
|
-
out.push(item);
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return out;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* 将常见内容值转换为 DOM 节点数组。
|
|
95
|
-
*
|
|
96
|
-
* 字符串会按 HTML 片段解析;函数会以 context 调用后继续归一化。
|
|
97
|
-
* @param {*} content 组件内容。
|
|
98
|
-
* @param {*} [context] 传给函数内容的上下文。
|
|
99
|
-
* @returns {Node[]}
|
|
100
|
-
*/
|
|
101
|
-
export function normalizeContentNodes(content, context) {
|
|
102
|
-
const value = typeof content === 'function' ? content(context) : content;
|
|
103
|
-
|
|
104
|
-
if (value == null || value === false || value === true) return [];
|
|
105
|
-
|
|
106
|
-
if (Array.isArray(value)) {
|
|
107
|
-
return value.flatMap((item) => normalizeContentNodes(item, context));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (isNode(value)) return [value];
|
|
111
|
-
|
|
112
|
-
if (typeof value === 'string') {
|
|
113
|
-
const template = document.createElement('template');
|
|
114
|
-
template.innerHTML = value;
|
|
115
|
-
return Array.from(template.content.childNodes);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return [document.createTextNode(String(value))];
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* 将 DOM 引用解析为节点列表。
|
|
123
|
-
*
|
|
124
|
-
* 支持的输入类型:
|
|
125
|
-
* - **Node**: 包装为单元素数组,如 `document.body`
|
|
126
|
-
* - **Element**: 包装为单元素数组,如 `div`
|
|
127
|
-
* - **string**: CSS 选择器,如 `'div'`, `'.class'`, `'#id'`
|
|
128
|
-
* - **Array**: 扁平化为节点数组,如 `[el1, [el2, el3]]`
|
|
129
|
-
* - **false/null/undefined**: 返回 null
|
|
130
|
-
*
|
|
131
|
-
* @param {Element|Node|string|Array|false|null|undefined} ref 元素引用、选择器、节点或空值。
|
|
132
|
-
* @param {string} [namespace='Component'] 错误命名空间。
|
|
133
|
-
* @returns {Node[]|null}
|
|
134
|
-
*/
|
|
135
|
-
export function resolveNodeList(ref, _namespace = 'Component') {
|
|
136
|
-
if (ref === false || ref == null) return null;
|
|
137
|
-
|
|
138
|
-
if (typeof ref === 'string') {
|
|
139
|
-
if (!canUseDOM()) return null;
|
|
140
|
-
const nodes = Array.from(document.querySelectorAll(ref));
|
|
141
|
-
return nodes.length > 0 ? nodes : null;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (Array.isArray(ref)) {
|
|
145
|
-
const nodes = flattenNodeArray(ref, []);
|
|
146
|
-
return nodes && nodes.length > 0 ? nodes : null;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (isNode(ref)) return [ref];
|
|
150
|
-
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* 将 DOM 引用解析为节点。
|
|
156
|
-
*
|
|
157
|
-
* 支持的输入类型:
|
|
158
|
-
* - **Node/Element**: 直接返回,如 `document.body`, `div`
|
|
159
|
-
* - **string**: CSS 选择器,如 `'div'`, `'.class'`, `'#id'`
|
|
160
|
-
* - **Array**: 返回第一个节点,如 `[el1, el2]`
|
|
161
|
-
* - **false/null/undefined**: 返回 null
|
|
162
|
-
*
|
|
163
|
-
* @param {Element|Node|string|Array|false|null|undefined} ref 元素引用、选择器、节点或空值。
|
|
164
|
-
* @param {string} [namespace='Component'] 错误命名空间。
|
|
165
|
-
* @returns {Node|null}
|
|
166
|
-
*/
|
|
167
|
-
export function resolveNode(ref, namespace = 'Component') {
|
|
168
|
-
if (isElement(ref) || isNode(ref)) return ref;
|
|
169
|
-
|
|
170
|
-
if (typeof ref === 'string') {
|
|
171
|
-
if (!canUseDOM()) return null;
|
|
172
|
-
return document.querySelector(ref);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (Array.isArray(ref)) {
|
|
176
|
-
const nodes = resolveNodeList(ref, namespace) || [];
|
|
177
|
-
return nodes[0] || null;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* 将 DOM 引用解析为元素。
|
|
185
|
-
*
|
|
186
|
-
* 支持的输入类型:
|
|
187
|
-
* - **Element**: 直接返回,如 `document.querySelector('#app')`
|
|
188
|
-
* - **string**: CSS 选择器,如 `'div'`, `'.class'`, `'#id'`
|
|
189
|
-
* - **Array**: 返回第一个 Element,如 `[el1, el2]`
|
|
190
|
-
* - **false/null/undefined**: 返回 null
|
|
191
|
-
*
|
|
192
|
-
* @param {Element|Node|string|Array|false|null|undefined} ref 元素引用、选择器、节点或空值。
|
|
193
|
-
* @param {string} [namespace='Component'] 错误命名空间。
|
|
194
|
-
* @returns {Element|null}
|
|
195
|
-
*/
|
|
196
|
-
export function resolveElement(ref, namespace = 'Component') {
|
|
197
|
-
if (!isContainerLike(ref) || ref === false || ref == null) return null;
|
|
198
|
-
|
|
199
|
-
if (typeof ref === 'string') {
|
|
200
|
-
if (!canUseDOM()) return null;
|
|
201
|
-
const element = document.querySelector(ref);
|
|
202
|
-
return isElement(element) ? element : null;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (Array.isArray(ref)) {
|
|
206
|
-
const nodes = resolveNodeList(ref, namespace);
|
|
207
|
-
return Array.isArray(nodes) ? firstElement(nodes) : null;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return isElement(ref) ? ref : null;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* 统一解析容器引用。
|
|
215
|
-
*
|
|
216
|
-
* @param {Element|Node|string|Array|false|null|undefined} container 容器引用、选择器、节点或数组。
|
|
217
|
-
* @param {string} [namespace='Component'] 错误命名空间。
|
|
218
|
-
* @param {'node'|'element'|'array'} [expect='element'] 期望返回类型。
|
|
219
|
-
* @returns {Node|Element|Node[]|null}
|
|
220
|
-
*/
|
|
221
|
-
export function resolveContainer(
|
|
222
|
-
container,
|
|
223
|
-
namespace = 'Component',
|
|
224
|
-
expect = 'element'
|
|
225
|
-
) {
|
|
226
|
-
if (!['node', 'element', 'array'].includes(expect)) {
|
|
227
|
-
throw new Error(
|
|
228
|
-
`${namespace}: expect must be one of 'node', 'element', 'array'.`
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (!isContainerLike(container)) return null;
|
|
233
|
-
|
|
234
|
-
if (expect === 'array') {
|
|
235
|
-
return resolveNodeList(container, namespace);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (expect === 'node') {
|
|
239
|
-
return resolveNode(container, namespace);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return resolveElement(container, namespace);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* 强制解析容器并要求返回值存在。
|
|
247
|
-
*
|
|
248
|
-
* @param {Element|Node|string|Array|false|null|undefined} container 容器引用、选择器、节点或数组。
|
|
249
|
-
* @param {string} [namespace='Component'] 错误命名空间。
|
|
250
|
-
* @param {'node'|'element'|'array'} [expect='element'] 期望返回类型。
|
|
251
|
-
* @returns {Node|Element|Node[]}
|
|
252
|
-
*/
|
|
253
|
-
export function requireContainer(
|
|
254
|
-
container,
|
|
255
|
-
namespace = 'Component',
|
|
256
|
-
expect = 'element'
|
|
257
|
-
) {
|
|
258
|
-
const resolved = resolveContainer(container, namespace, expect);
|
|
259
|
-
if (resolved == null) {
|
|
260
|
-
throw new Error(`${namespace}: container not found.`);
|
|
261
|
-
}
|
|
262
|
-
return resolved;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* 判断是否为组件可渲染内容。
|
|
267
|
-
* @param {*} value 需要判断的值。
|
|
268
|
-
* @returns {boolean}
|
|
269
|
-
*/
|
|
270
|
-
export function isRenderableContent(value) {
|
|
271
|
-
return (
|
|
272
|
-
value == null ||
|
|
273
|
-
typeof value === 'string' ||
|
|
274
|
-
typeof value === 'number' ||
|
|
275
|
-
typeof value === 'boolean' ||
|
|
276
|
-
typeof value === 'function' ||
|
|
277
|
-
Array.isArray(value) ||
|
|
278
|
-
isNode(value)
|
|
279
|
-
);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* 创建通用加载状态节点。
|
|
284
|
-
* @param {string} [className='j-loading is-active'] 容器类名。
|
|
285
|
-
* @returns {HTMLElement}
|
|
286
|
-
*/
|
|
287
|
-
export function createLoading(className = 'j-loading is-active') {
|
|
288
|
-
return jsx('div', {
|
|
289
|
-
className,
|
|
290
|
-
'aria-live': 'polite',
|
|
291
|
-
children: jsx('div', { className: 'loading-spinner' }),
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* 根据 CSS 选择器获取第一个匹配的元素。
|
|
297
|
-
* @param {string} selector CSS 选择器。
|
|
298
|
-
* @param {Document|Element} [context=document] 查询范围。
|
|
299
|
-
* @returns {Element|null}
|
|
300
|
-
*/
|
|
301
|
-
export function q(selector, context = document) {
|
|
302
|
-
return context.querySelector(selector);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* 根据 CSS 选择器获取所有匹配的元素。
|
|
307
|
-
* @param {string} selector CSS 选择器。
|
|
308
|
-
* @param {Document|Element} [context=document] 查询范围。
|
|
309
|
-
* @returns {Element[]}
|
|
310
|
-
*/
|
|
311
|
-
export function all(selector, context = document) {
|
|
312
|
-
return Array.from(context.querySelectorAll(selector));
|
|
313
|
-
}
|
package/src/utilities/events.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
const NOOP = () => {};
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 绑定 DOM 事件并返回一次性解绑函数。
|
|
5
|
-
*
|
|
6
|
-
* 语法糖,不做target等参数的校验。
|
|
7
|
-
*
|
|
8
|
-
* @param {EventTarget} target 事件目标。
|
|
9
|
-
* @param {string} type 事件类型。
|
|
10
|
-
* @param {EventListenerOrEventListenerObject} handler 事件处理器。
|
|
11
|
-
* @param {AddEventListenerOptions|boolean} [options] 事件选项。
|
|
12
|
-
* @returns {Function} 解绑函数,可重复调用。
|
|
13
|
-
*/
|
|
14
|
-
export function listen(target, type, handler, options) {
|
|
15
|
-
let active = true;
|
|
16
|
-
target.addEventListener(type, handler, options);
|
|
17
|
-
return () => {
|
|
18
|
-
if (!active) return;
|
|
19
|
-
active = false;
|
|
20
|
-
target.removeEventListener(type, handler, options);
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* 创建实例级事件管理器。
|
|
26
|
-
*
|
|
27
|
-
* - `on()` 绑定事件并自动记录解绑函数。
|
|
28
|
-
* - 同一个 key 重复绑定时会先解绑旧事件,避免组件重渲染时重复监听。
|
|
29
|
-
* - `off(key)` 和 `clear()` 用于跨作用域精确释放。
|
|
30
|
-
* @returns {{on: Function, off: Function, clear: Function, size: Function}}
|
|
31
|
-
*/
|
|
32
|
-
export function createEventManager() {
|
|
33
|
-
const records = new Map();
|
|
34
|
-
|
|
35
|
-
const off = (key) => {
|
|
36
|
-
const dispose = records.get(key);
|
|
37
|
-
if (!dispose) return false;
|
|
38
|
-
dispose();
|
|
39
|
-
records.delete(key);
|
|
40
|
-
return true;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
on(key, target, type, handler, options) {
|
|
45
|
-
if (typeof key !== 'string' || key.trim() === '') {
|
|
46
|
-
throw new TypeError('EventManager.on: key expects a non-empty string.');
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
off(key);
|
|
50
|
-
|
|
51
|
-
if (target == null) return NOOP;
|
|
52
|
-
|
|
53
|
-
const dispose = listen(target, type, handler, options);
|
|
54
|
-
records.set(key, dispose);
|
|
55
|
-
return dispose;
|
|
56
|
-
},
|
|
57
|
-
|
|
58
|
-
off,
|
|
59
|
-
|
|
60
|
-
clear() {
|
|
61
|
-
for (const dispose of records.values()) dispose();
|
|
62
|
-
records.clear();
|
|
63
|
-
},
|
|
64
|
-
|
|
65
|
-
size() {
|
|
66
|
-
return records.size;
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
}
|
package/src/utilities/http.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 发送 JSON POST 请求并解析 JSON 响应。
|
|
3
|
-
* @param {string} url 请求地址。
|
|
4
|
-
* @param {*} body 请求体,会被 JSON.stringify。
|
|
5
|
-
* @param {RequestInit} [options] 透传给 fetch 的请求配置。
|
|
6
|
-
* @returns {Promise<any>} JSON 响应体。
|
|
7
|
-
*/
|
|
8
|
-
export async function postJson(url, body, options = {}) {
|
|
9
|
-
const headers = new Headers(options.headers);
|
|
10
|
-
|
|
11
|
-
if (!headers.has('Content-Type')) {
|
|
12
|
-
headers.set('Content-Type', 'application/json');
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const res = await fetch(url, {
|
|
16
|
-
...options,
|
|
17
|
-
method: options.method || 'POST',
|
|
18
|
-
headers,
|
|
19
|
-
body: JSON.stringify(body),
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
return res.json();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 当前站点 WordPress REST API 根地址。
|
|
27
|
-
* @type {string}
|
|
28
|
-
*/
|
|
29
|
-
export const restUrl =
|
|
30
|
-
typeof window !== 'undefined' ? `${window.location.origin}/wp-json` : '';
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import { q } from './dom.js';
|
|
2
|
-
import { listen } from './events.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* 创建 DOM 元素。
|
|
6
|
-
*
|
|
7
|
-
* 这是历史工具函数,新组件优先使用 signal.js/vanilla-signal 的 jsx 渲染。
|
|
8
|
-
* @param {string} tagName 标签名。
|
|
9
|
-
* @param {object} [options={}] 元素配置。
|
|
10
|
-
* @param {string} [options.is] 自定义内建元素扩展名。
|
|
11
|
-
* @param {string} [options.className] className。
|
|
12
|
-
* @param {string} [options.id] id。
|
|
13
|
-
* @param {Record<string, any>} [options.attrs] attribute 集合。
|
|
14
|
-
* @param {Record<string, any>} [options.style] 行内样式集合。
|
|
15
|
-
* @param {Record<string, Function>} [options.events] 事件处理集合。
|
|
16
|
-
* @param {string} [options.text] textContent。
|
|
17
|
-
* @param {string} [options.html] innerHTML,调用方需自行处理 XSS 风险。
|
|
18
|
-
* @param {Node|Node[]|string} [options.children] 子节点或 HTML 字符串。
|
|
19
|
-
* @param {boolean|Function} [options.dependency] 依赖条件,false 时返回空字符串。
|
|
20
|
-
* @returns {HTMLElement|string}
|
|
21
|
-
* @legacy 废弃,请使用 h() 或 jsx() 替代。
|
|
22
|
-
*/
|
|
23
|
-
export function el(tagName, options = {}) {
|
|
24
|
-
if (typeof document === 'undefined') {
|
|
25
|
-
throw new Error('el() can only be used in browser environment.');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (
|
|
29
|
-
options.dependency === false ||
|
|
30
|
-
(typeof options.dependency === 'function' && !options.dependency())
|
|
31
|
-
) {
|
|
32
|
-
return '';
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const { is, className, id, attrs, style, events, text, html, children } =
|
|
36
|
-
options;
|
|
37
|
-
const element = is
|
|
38
|
-
? document.createElement(tagName, { is })
|
|
39
|
-
: document.createElement(tagName);
|
|
40
|
-
|
|
41
|
-
if (className) element.className = className;
|
|
42
|
-
if (id) element.id = id;
|
|
43
|
-
|
|
44
|
-
if (attrs && typeof attrs === 'object') {
|
|
45
|
-
for (const [key, value] of Object.entries(attrs)) {
|
|
46
|
-
if (value != null) element.setAttribute(key, String(value));
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (style && typeof style === 'object') {
|
|
51
|
-
for (const [key, value] of Object.entries(style)) {
|
|
52
|
-
if (value != null) element.style[key] = value;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (events && typeof events === 'object') {
|
|
57
|
-
for (const [type, handler] of Object.entries(events)) {
|
|
58
|
-
if (typeof handler === 'function') {
|
|
59
|
-
listen(element, type, handler);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (children !== undefined) {
|
|
65
|
-
if (Array.isArray(children)) {
|
|
66
|
-
element.append(...children);
|
|
67
|
-
} else if (typeof children === 'string') {
|
|
68
|
-
element.innerHTML = children;
|
|
69
|
-
} else if (children instanceof Node) {
|
|
70
|
-
element.appendChild(children);
|
|
71
|
-
}
|
|
72
|
-
} else if (html !== undefined) {
|
|
73
|
-
element.innerHTML = html;
|
|
74
|
-
} else if (text !== undefined) {
|
|
75
|
-
element.textContent = text;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return element;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* 当目标元素进入可视区域时执行渲染回调,仅执行一次后自动清理。
|
|
83
|
-
* 支持传入选择器字符串或 Element 对象,即使元素尚未挂载到 DOM 也能工作。
|
|
84
|
-
*
|
|
85
|
-
* @param {string|Element} target CSS 选择器或 DOM 元素。
|
|
86
|
-
* @param {Function} renderCallback 渲染回调函数,仅执行一次。
|
|
87
|
-
* @param {Object} [options] 配置项。
|
|
88
|
-
* @param {number|number[]} [options.threshold=0.1] 触发阈值。
|
|
89
|
-
* @param {string} [options.rootMargin="0px"] 根边距。
|
|
90
|
-
* @param {Element|null} [options.root=null] 根容器。
|
|
91
|
-
* @param {boolean} [options.waitForDOM=true] 是否等待元素出现在 DOM 中。
|
|
92
|
-
* @returns {Function} 停止观察的清理函数。
|
|
93
|
-
*/
|
|
94
|
-
export function lazyRender(target, renderCallback, options = {}) {
|
|
95
|
-
const {
|
|
96
|
-
threshold = 0.1,
|
|
97
|
-
rootMargin = '0px',
|
|
98
|
-
root = null,
|
|
99
|
-
waitForDOM = true,
|
|
100
|
-
} = options;
|
|
101
|
-
const observerOptions = { threshold, rootMargin, root };
|
|
102
|
-
|
|
103
|
-
let cleanup = () => {};
|
|
104
|
-
let element = null;
|
|
105
|
-
|
|
106
|
-
if (typeof target === 'string') {
|
|
107
|
-
element = q(target);
|
|
108
|
-
} else if (typeof Element !== 'undefined' && target instanceof Element) {
|
|
109
|
-
element = target;
|
|
110
|
-
} else {
|
|
111
|
-
console.warn('lazyRender: target 必须是 CSS 选择器字符串或 DOM 元素');
|
|
112
|
-
return cleanup;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (element && document.body.contains(element)) {
|
|
116
|
-
cleanup = observeAndRender(element, renderCallback, observerOptions);
|
|
117
|
-
return cleanup;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (!waitForDOM) {
|
|
121
|
-
renderCallback();
|
|
122
|
-
return cleanup;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const observer = new MutationObserver(() => {
|
|
126
|
-
let found = false;
|
|
127
|
-
|
|
128
|
-
if (typeof target === 'string') {
|
|
129
|
-
const current = q(target);
|
|
130
|
-
if (current && document.body.contains(current)) {
|
|
131
|
-
found = true;
|
|
132
|
-
element = current;
|
|
133
|
-
}
|
|
134
|
-
} else if (document.body.contains(target)) {
|
|
135
|
-
found = true;
|
|
136
|
-
element = target;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (found) {
|
|
140
|
-
observer.disconnect();
|
|
141
|
-
cleanup = observeAndRender(element, renderCallback, observerOptions);
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
observer.observe(document.documentElement, {
|
|
146
|
-
childList: true,
|
|
147
|
-
subtree: true,
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
return () => {
|
|
151
|
-
observer.disconnect();
|
|
152
|
-
cleanup();
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function observeAndRender(element, renderCallback, observerOptions) {
|
|
157
|
-
if (!window.IntersectionObserver) {
|
|
158
|
-
renderCallback();
|
|
159
|
-
return () => {};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const io = new IntersectionObserver((entries) => {
|
|
163
|
-
for (const entry of entries) {
|
|
164
|
-
if (entry.isIntersecting) {
|
|
165
|
-
io.unobserve(element);
|
|
166
|
-
io.disconnect();
|
|
167
|
-
renderCallback();
|
|
168
|
-
break;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}, observerOptions);
|
|
172
|
-
|
|
173
|
-
io.observe(element);
|
|
174
|
-
|
|
175
|
-
return () => {
|
|
176
|
-
io.unobserve(element);
|
|
177
|
-
io.disconnect();
|
|
178
|
-
};
|
|
179
|
-
}
|
package/src/utilities/storage.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 读取 Cookie。
|
|
3
|
-
* @param {string} name Cookie 名称。
|
|
4
|
-
* @returns {string|null}
|
|
5
|
-
*/
|
|
6
|
-
export function getCookie(name) {
|
|
7
|
-
if (typeof document === 'undefined') return null;
|
|
8
|
-
|
|
9
|
-
const value = `; ${document.cookie}`;
|
|
10
|
-
const parts = value.split(`; ${name}=`);
|
|
11
|
-
if (parts.length === 2) return parts.pop().split(';').shift();
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* 写入 Cookie。
|
|
17
|
-
* @param {string} name Cookie 名称。
|
|
18
|
-
* @param {string} value Cookie 值。
|
|
19
|
-
* @param {number} [seconds=86400] 有效期,单位秒。
|
|
20
|
-
* @returns {boolean} 写入后是否能读取到相同值。
|
|
21
|
-
*/
|
|
22
|
-
export function setCookie(name, value, seconds = 60 * 60 * 24) {
|
|
23
|
-
if (typeof document === 'undefined') return false;
|
|
24
|
-
|
|
25
|
-
const expires = new Date(Date.now() + seconds * 1000).toUTCString();
|
|
26
|
-
document.cookie = [
|
|
27
|
-
`${name}=${value}`,
|
|
28
|
-
`expires=${expires}`,
|
|
29
|
-
'path=/',
|
|
30
|
-
'sameSite=strict',
|
|
31
|
-
].join('; ');
|
|
32
|
-
|
|
33
|
-
return getCookie(name) === value;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* 删除 Cookie。
|
|
38
|
-
* @param {string} name Cookie 名称。
|
|
39
|
-
* @returns {boolean} 删除后是否不可读取。
|
|
40
|
-
*/
|
|
41
|
-
export function removeCookie(name) {
|
|
42
|
-
if (typeof document === 'undefined') return true;
|
|
43
|
-
|
|
44
|
-
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
|
45
|
-
return !getCookie(name);
|
|
46
|
-
}
|
|
File without changes
|