clickgo 3.16.16 → 3.16.18

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 (63) hide show
  1. package/README.md +1 -1
  2. package/dist/app/demo/form/method/native/native.js +26 -0
  3. package/dist/app/demo/form/method/native/native.xml +2 -0
  4. package/dist/clickgo.d.ts +17 -0
  5. package/dist/clickgo.js +1 -1
  6. package/dist/control/arteditor.cgc +0 -0
  7. package/dist/control/box.cgc +0 -0
  8. package/dist/control/captcha.cgc +0 -0
  9. package/dist/control/common.cgc +0 -0
  10. package/dist/control/desc.cgc +0 -0
  11. package/dist/control/drawer.cgc +0 -0
  12. package/dist/control/echarts.cgc +0 -0
  13. package/dist/control/form.cgc +0 -0
  14. package/dist/control/iconview.cgc +0 -0
  15. package/dist/control/jodit.cgc +0 -0
  16. package/dist/control/map.cgc +0 -0
  17. package/dist/control/monaco.cgc +0 -0
  18. package/dist/control/mpegts.cgc +0 -0
  19. package/dist/control/nav.cgc +0 -0
  20. package/dist/control/page.cgc +0 -0
  21. package/dist/control/pdf.cgc +0 -0
  22. package/dist/control/property.cgc +0 -0
  23. package/dist/control/qrcode.cgc +0 -0
  24. package/dist/control/table.cgc +0 -0
  25. package/dist/control/task.cgc +0 -0
  26. package/dist/control/tplink.cgc +0 -0
  27. package/dist/control/tuieditor.cgc +0 -0
  28. package/dist/control/tuiviewer.cgc +0 -0
  29. package/dist/control/xterm.cgc +0 -0
  30. package/dist/index.d.ts +51 -0
  31. package/dist/lib/control.d.ts +53 -0
  32. package/dist/lib/core.d.ts +47 -0
  33. package/dist/lib/dom.d.ts +74 -0
  34. package/dist/lib/dom.js +7 -7
  35. package/dist/lib/form.d.ts +222 -0
  36. package/dist/lib/fs.d.ts +35 -0
  37. package/dist/lib/fs.js +2 -2
  38. package/dist/lib/native.d.ts +36 -0
  39. package/dist/lib/native.js +8 -0
  40. package/dist/lib/storage.d.ts +6 -0
  41. package/dist/lib/task.d.ts +32 -0
  42. package/dist/lib/task.js +7 -1
  43. package/dist/lib/theme.d.ts +8 -0
  44. package/dist/lib/tool.d.ts +120 -0
  45. package/dist/lib/zip.d.ts +40 -0
  46. package/dist/theme/blue.cgt +0 -0
  47. package/dist/theme/byterun.cgt +0 -0
  48. package/dist/theme/light.cgt +0 -0
  49. package/package.json +7 -5
  50. package/dist/clickgo.ts +0 -68
  51. package/dist/index.ts +0 -282
  52. package/dist/lib/control.ts +0 -751
  53. package/dist/lib/core.ts +0 -1145
  54. package/dist/lib/dom.ts +0 -2728
  55. package/dist/lib/form.ts +0 -3829
  56. package/dist/lib/fs.ts +0 -1324
  57. package/dist/lib/native.ts +0 -236
  58. package/dist/lib/storage.ts +0 -229
  59. package/dist/lib/task.ts +0 -2160
  60. package/dist/lib/theme.ts +0 -199
  61. package/dist/lib/tool.ts +0 -1278
  62. package/dist/lib/zip.ts +0 -444
  63. package/eslint.config.js +0 -22
package/dist/lib/dom.ts DELETED
@@ -1,2728 +0,0 @@
1
- /**
2
- * Copyright 2024 Han Guoshuai <zohegs@gmail.com>
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * https://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
- import * as types from '../../types';
17
- import * as clickgo from '../clickgo';
18
- import * as form from './form';
19
- import * as core from './core';
20
- import * as tool from './tool';
21
-
22
- /** --- style list 的 div --- */
23
- const topClass: string[] = ['#cg-form-list', '#cg-pop-list', '#cg-notify', '#cg-alert', '#cg-simpletask', '#cg-launcher', '#cg-confirm'];
24
- function classUnfold(after?: string, out: string[] = []): string {
25
- const arr: string[] = [];
26
- for (const name of topClass) {
27
- if (out.includes(name)) {
28
- continue;
29
- }
30
- arr.push(name + (after ? (' ' + after) : ''));
31
- }
32
- return arr.join(', ');
33
- }
34
-
35
- const styleList: HTMLDivElement = document.createElement('div');
36
- styleList.style.display = 'none';
37
- document.getElementsByTagName('body')[0].appendChild(styleList);
38
- styleList.insertAdjacentHTML('beforeend', '<style id=\'cg-global-cursor\'></style>');
39
- styleList.insertAdjacentHTML('beforeend', `<style id='cg-global'>
40
- ${classUnfold()} {-webkit-user-select: none; user-select: none; cursor: default; box-sizing: border-box;}
41
- ${topClass.slice(0, 4).join(', ')} {left: 0; top: 0; width: 0; height: 0; position: absolute;}
42
- ${classUnfold('img')} {vertical-align: bottom;}
43
- ${classUnfold('::selection', ['#cg-launcher'])} {background-color: rgba(0, 0, 0, .1);}
44
- ${classUnfold('*')}, ${classUnfold('*::after')}, ${classUnfold('*::before')} {box-sizing: border-box; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); flex-shrink: 0;}
45
- ${classUnfold(' > div')} {font-family: var(--g-family); font-size: var(--g-size); line-height: 1; -webkit-font-smoothing: antialiased;}
46
- </style>`);
47
-
48
- /**
49
- * --- 判断一个元素是否还存在于页面当中 ---
50
- * @param el 要判断的元素
51
- */
52
- export function inPage(el: HTMLElement): boolean {
53
- return document.body.contains(el);
54
- }
55
-
56
- // --- 计算 dpi ---
57
- const dpiDiv = document.createElement('div');
58
- dpiDiv.style.visibility = 'hidden';
59
- dpiDiv.style.width = '1in';
60
- document.getElementsByTagName('body')[0].appendChild(dpiDiv);
61
- export const dpi: number = dpiDiv.getBoundingClientRect().width;
62
- dpiDiv.remove();
63
-
64
- /** --- 全局 cursor 设置的 style 标签 --- */
65
- let globalCursorStyle: HTMLStyleElement;
66
- /**
67
- * --- 设置全局鼠标样式 ---
68
- * @param type 样式或留空,留空代表取消
69
- */
70
- export function setGlobalCursor(type?: string): void {
71
- if (!globalCursorStyle) {
72
- globalCursorStyle = document.getElementById('cg-global-cursor') as HTMLStyleElement;
73
- }
74
- if (type) {
75
- globalCursorStyle.innerHTML = `* {cursor: ${type} !important;}`;
76
- }
77
- else {
78
- globalCursorStyle.innerHTML = '';
79
- }
80
- }
81
-
82
- /** --- 最后一次 touchstart 的时间戳 */
83
- let lastTouchTime: number = 0;
84
- // --- 添加 touchstart 事件,既优化了点击行为,也记录了 touch 的时间戳信息 ---
85
- document.addEventListener('touchstart', function() {
86
- lastTouchTime = Date.now();
87
- }, {
88
- 'passive': true
89
- });
90
-
91
- /**
92
- * --- 判断当前的事件是否是含有 touch 的设备触发的,如果当前就是 touch 则直接返回 false(false 代表 OK,true 代表 touch 设备却触发了 mouse 事件) ---
93
- */
94
- export function hasTouchButMouse(e: MouseEvent | TouchEvent | PointerEvent): boolean {
95
- if (e instanceof TouchEvent || e.type === 'touchstart') {
96
- return false;
97
- }
98
- if (((e as any).pointerType === 'touch') && (e.type === 'contextmenu')) {
99
- // --- 当前是 mouse 但是却是 touch 触发的 ---
100
- return true;
101
- }
102
- const now = Date.now();
103
- if (now - lastTouchTime < 1_000 * 60) {
104
- // --- 当前是 mouse 但是 10000ms 内有 touch start ---
105
- return true;
106
- }
107
- return false;
108
- }
109
-
110
- /**
111
- * --- 创建任务时连同一起创建的 style 标签,App 模式下无效 ---
112
- * @param taskId 任务 id
113
- */
114
- export function createToStyleList(taskId: number): void {
115
- styleList.insertAdjacentHTML('beforeend', `<div id="cg-style-task${taskId}"><div class="cg-style-control"></div><div class="cg-style-theme"></div><style class="cg-style-global"></style><div class="cg-style-form"></div></div>`);
116
- }
117
-
118
- /**
119
- * --- 任务结束时需要移除 task 的所有 style,App 模式下无效 ---
120
- * @param taskId 任务 id
121
- */
122
- export function removeFromStyleList(taskId: number): void {
123
- document.getElementById('cg-style-task' + taskId.toString())?.remove();
124
- }
125
-
126
- /**
127
- * --- 将 style 内容写入 dom,App 模式下无效 ---
128
- * @param taskId 当前任务 ID
129
- * @param style 样式内容
130
- * @param type 插入的类型
131
- * @param formId 当前窗体 ID(global 下可空,theme 下为主题唯一标识符,control 下为控件名)
132
- * @param panelId 若是 panel 中创建的则需要指定 panelId,仅 type 为 form 有效
133
- */
134
- export function pushStyle(taskId: number, style: string, type: 'global' | 'theme' | 'control' | 'form' = 'global', formId: number | string = 0, panelId?: number): void {
135
- const el = document.querySelector(`#cg-style-task${taskId} > .cg-style-${type}`);
136
- if (!el) {
137
- return;
138
- }
139
- if (type === 'global') {
140
- el.innerHTML = style;
141
- }
142
- else if (type === 'theme' || type === 'control') {
143
- el.insertAdjacentHTML('beforeend', `<style data-name="${formId}">${style}</style>`);
144
- }
145
- else {
146
- // --- form ---
147
- el.insertAdjacentHTML('beforeend', `<style class="cg-style-form${formId}" data-panel="${panelId ? panelId.toString() : ''}">${style}</style>`);
148
- }
149
- }
150
-
151
- /**
152
- * --- 移除 style 样式 dom,App 模式下无效 ---
153
- * @param taskId 要移除的任务 ID
154
- * @param type 移除的类型
155
- * @param formId 要移除的窗体 ID
156
- * @param panelId type 为 form 模式下若不指定则当前 form 包含 panel 的样式都会被移除
157
- */
158
- export function removeStyle(taskId: number, type: 'global' | 'theme' | 'control' | 'form' = 'global', formId: number | string = 0, panelId?: number): void {
159
- const styleTask = document.getElementById('cg-style-task' + taskId.toString());
160
- if (!styleTask) {
161
- return;
162
- }
163
- if (type === 'global') {
164
- const el = styleTask.querySelector(`.cg-style-global`);
165
- if (!el) {
166
- return;
167
- }
168
- el.innerHTML = '';
169
- }
170
- else if (type === 'theme' || type === 'control') {
171
- if (formId === 0) {
172
- const el = styleTask.querySelector(`.cg-style-${type}`);
173
- if (!el) {
174
- return;
175
- }
176
- el.innerHTML = '';
177
- }
178
- else {
179
- const elist = styleTask.querySelectorAll(`.cg-style-${type} > [data-name='${formId}']`);
180
- for (let i = 0; i < elist.length; ++i) {
181
- elist.item(i).remove();
182
- }
183
- }
184
- }
185
- else {
186
- // --- form ---
187
- const elist = styleTask.querySelectorAll('.cg-style-form' + formId.toString() + (panelId ? '[data-panel="' + panelId.toString() + '"]' : ''));
188
- for (let i = 0; i < elist.length; ++i) {
189
- elist.item(i).remove();
190
- }
191
- }
192
- }
193
-
194
- /**
195
- * --- 获取当前任务中子类有几个子元素 ---
196
- * @param taskId 任务 ID
197
- * @param type 类型
198
- */
199
- export function getStyleCount(taskId: number, type: 'theme' | 'control' | 'form'): number {
200
- return document.querySelectorAll(`#cg-style-task${taskId} > .cg-style-${type} > style`).length;
201
- }
202
-
203
- // ---------------------
204
- // --- watchPosition ---
205
- // ---------------------
206
-
207
- /**
208
- * --- 监听中的标签对象,对应 formId -> 数组列表 ---
209
- */
210
- const watchPositionObjects: Record<
211
- /** --- formId --- */
212
- string,
213
- Record<
214
- /** --- panelId 或 default --- */
215
- string,
216
- Record<
217
- /** --- index 值 --- */
218
- string,
219
- types.IWatchPositionItem
220
- >
221
- >
222
- > = {};
223
-
224
- /** --- 监视元素的 data-cg-poindex --- */
225
- let watchPositionIndex: number = 0;
226
-
227
- /**
228
- * --- 添加监视 Element 对象位置,元素移除后自动停止监视,已经监视中的不会再次监视,请短时间使用(虽然本方法也可以监听 element 的大小改变,但这是监听位置改变的副产品,如果仅仅监听大小改变请使用效率更高的 watch size) ---
229
- * @param el 要监视的大小
230
- * @param cb 回调函数
231
- * @param immediate 立刻先执行一次回调
232
- */
233
- export function watchPosition(
234
- el: HTMLElement,
235
- cb: (state: {
236
- 'position': boolean;
237
- 'size': boolean;
238
- }) => void | Promise<void>,
239
- immediate: boolean = false
240
- ): boolean {
241
- if (isWatchPosition(el)) {
242
- return false;
243
- }
244
- if (immediate) {
245
- try {
246
- const r = cb({
247
- 'position': false,
248
- 'size': false
249
- });
250
- if (r instanceof Promise) {
251
- r.catch(function(e) {
252
- console.log('dom.watchPosition', e);
253
- });
254
- }
255
- }
256
- catch (e) {
257
- console.log('dom.watchPosition', e);
258
- }
259
- }
260
- const formWrap = findParentByData(el, 'form-id');
261
- if (!formWrap) {
262
- return false;
263
- }
264
- const formId = formWrap.dataset.formId!;
265
- // --- 获取监视标签的所属 panel ---
266
- const panelWrap = findParentByData(el, 'panel-id');
267
- const panelId = panelWrap ? panelWrap.dataset.panelId! : 'default';
268
- // --- 创建 object ---
269
- if (!watchPositionObjects[formId]) {
270
- watchPositionObjects[formId] = {};
271
- }
272
- if (!watchPositionObjects[formId][panelId]) {
273
- watchPositionObjects[formId][panelId] = {};
274
- }
275
- watchPositionObjects[formId][panelId][watchPositionIndex] = {
276
- 'el': el,
277
- 'rect': el.getBoundingClientRect(),
278
- 'handler': cb
279
- };
280
- el.dataset.cgPoindex = watchPositionIndex.toString();
281
- ++watchPositionIndex;
282
- return true;
283
- }
284
-
285
- /**
286
- * --- 移除监视 Element 对象位置 ---
287
- * @param el 要移除监视
288
- */
289
- export function unwatchPosition(el: HTMLElement): void {
290
- const index = el.dataset.cgPoindex;
291
- if (index === undefined) {
292
- return;
293
- }
294
- const formWrap = findParentByData(el, 'form-id');
295
- if (!formWrap) {
296
- return;
297
- }
298
- const formId = formWrap.dataset.formId!;
299
- // --- 获取监视标签的所属 panel ---
300
- const panelWrap = findParentByData(el, 'panel-id');
301
- const panelId = panelWrap ? panelWrap.dataset.panelId! : 'default';
302
- el.removeAttribute('data-cg-poindex');
303
- delete watchPositionObjects[formId][panelId][index];
304
- if (Object.keys(watchPositionObjects[formId][panelId]).length) {
305
- return;
306
- }
307
- delete watchPositionObjects[formId][panelId];
308
- if (Object.keys(watchPositionObjects[formId]).length) {
309
- return;
310
- }
311
- delete watchPositionObjects[formId];
312
- }
313
-
314
- /**
315
- * --- 检测一个标签是否正在被 watchSize ---
316
- * @param el 要检测的标签
317
- */
318
- export function isWatchPosition(el: HTMLElement): boolean {
319
- return el.dataset.cgPoindex ? true : false;
320
- }
321
-
322
- /**
323
- * --- 清除某个窗体的所有 watch position 监视,虽然窗体结束后相关监视永远不会再被执行,但是会形成冗余,App 模式下无效 ---
324
- * @param formId 窗体 id
325
- * @param panelId 若指定则只清除当前窗体的某个 panel 的 watch
326
- */
327
- export function clearWatchPosition(formId: number | string, panelId?: number): void {
328
- if (!watchPositionObjects[formId]) {
329
- return;
330
- }
331
- for (const panel in watchPositionObjects[formId]) {
332
- if (panelId) {
333
- if (panel !== panelId.toString()) {
334
- continue;
335
- }
336
- }
337
- for (const index in watchPositionObjects[formId][panel]) {
338
- const item = watchPositionObjects[formId][panel][index];
339
- item.el.removeAttribute('data-cg-poindex');
340
- }
341
- delete watchPositionObjects[formId][panel];
342
- }
343
- if (Object.keys(watchPositionObjects[formId]).length) {
344
- return;
345
- }
346
- delete watchPositionObjects[formId];
347
- }
348
-
349
- // -----------------
350
- // --- watchSize ---
351
- // -----------------
352
-
353
- /** --- 被监视中的元素 --- */
354
- const watchSizeList: Record<string, types.IWatchSizeItem> = {};
355
-
356
- /**
357
- * --- 获取当前 watch size 中的元素总数 ---
358
- * @param taskId 留空则获取全部总数 ---
359
- */
360
- export function getWatchSizeCount(taskId?: number): number {
361
- if (!taskId) {
362
- return Object.keys(watchSizeList).length;
363
- }
364
- let count = 0;
365
- for (const id in watchSizeList) {
366
- if (watchSizeList[id].taskId !== taskId) {
367
- continue;
368
- }
369
- ++count;
370
- }
371
- return count;
372
- }
373
-
374
- /** --- 监视元素的 data-cg-roindex --- */
375
- let watchSizeIndex: number = 0;
376
-
377
- // --- 创建 ro 对象 ---
378
- const resizeObserver = new ResizeObserver(function(entries): void {
379
- for (const entrie of entries) {
380
- const el = entrie.target as HTMLElement;
381
- if (!document.body.contains(el)) {
382
- resizeObserver.unobserve(el);
383
- if (watchSizeList[el.dataset.cgRoindex!]) {
384
- delete watchSizeList[el.dataset.cgRoindex!];
385
- }
386
- continue;
387
- }
388
- const item = watchSizeList[el.dataset.cgRoindex!];
389
- try {
390
- const r = item.handler();
391
- if (r instanceof Promise) {
392
- r.catch(function(e) {
393
- console.log('ResizeObserver', e);
394
- });
395
- }
396
- }
397
- catch (e) {
398
- console.log('ResizeObserver', e);
399
- }
400
- }
401
- });
402
-
403
- /**
404
- * --- 添加监视 Element 对象大小,元素移除后自动停止监视(浏览器原生效果),已经监视中的不会再次监视 ---
405
- * @param el 要监视的大小
406
- * @param cb 回调函数
407
- * @param immediate 立刻先执行一次回调
408
- * @param taskId 归属到一个任务里可留空,App 模式下无效
409
- */
410
- export function watchSize(
411
- el: HTMLElement,
412
- cb: () => void | Promise<void>,
413
- immediate: boolean = false,
414
- taskId?: number
415
- ): boolean {
416
- if (isWatchSize(el)) {
417
- return false;
418
- }
419
- if (immediate) {
420
- try {
421
- const r = cb();
422
- if (r instanceof Promise) {
423
- r.catch(function(e) {
424
- console.log('dom.watchSize', e);
425
- });
426
- }
427
- }
428
- catch (e) {
429
- console.log('dom.watchSize', e);
430
- }
431
- }
432
- resizeObserver.observe(el);
433
- watchSizeList[watchSizeIndex] = {
434
- 'el': el,
435
- 'handler': cb,
436
- 'taskId': taskId
437
- };
438
- el.dataset.cgRoindex = watchSizeIndex.toString();
439
- ++watchSizeIndex;
440
- return true;
441
- }
442
-
443
- /**
444
- * --- 移除监视 Element 对象大小 ---
445
- * @param el 要移除监视
446
- * @param taskId 校验任务 id,App 模式下无效
447
- */
448
- export function unwatchSize(el: HTMLElement, taskId?: number): void {
449
- const index = el.dataset.cgRoindex;
450
- if (index === undefined) {
451
- return;
452
- }
453
- const item = watchSizeList[index];
454
- if (taskId && item.taskId !== taskId) {
455
- return;
456
- }
457
- resizeObserver.unobserve(el);
458
- el.removeAttribute('data-cg-roindex');
459
- delete watchSizeList[index];
460
- }
461
-
462
- /**
463
- * --- 检测一个标签是否正在被 watchSize ---
464
- * @param el 要检测的标签
465
- */
466
- export function isWatchSize(el: HTMLElement): boolean {
467
- return el.dataset.cgRoindex ? true : false;
468
- }
469
-
470
- /**
471
- * --- 清除某个任务的所有 watch size 监视,App 模式下无效 ---
472
- * @param taskId 任务 id
473
- */
474
- export function clearWatchSize(taskId: number): void {
475
- for (const index in watchSizeList) {
476
- const item = watchSizeList[index];
477
- if (taskId !== item.taskId) {
478
- continue;
479
- }
480
- resizeObserver.unobserve(item.el);
481
- item.el.removeAttribute('data-cg-roindex');
482
- delete watchSizeList[index];
483
- }
484
- }
485
-
486
- // -------------
487
- // --- watch ---
488
- // -------------
489
-
490
- /** --- 监视 dom 变动中的元素 */
491
- const watchList: Record<string, types.IWatchItem> = {};
492
-
493
- /**
494
- * --- 获取当前 watch 中的元素总数 ---
495
- * @param taskId 留空则获取全部总数 ---
496
- */
497
- export function getWatchCount(taskId?: number): number {
498
- if (!taskId) {
499
- return Object.keys(watchList).length;
500
- }
501
- let count = 0;
502
- for (const id in watchList) {
503
- if (watchList[id].taskId !== taskId) {
504
- continue;
505
- }
506
- ++count;
507
- }
508
- return count;
509
- }
510
-
511
- /** --- 监视元素的 data-cg-moindex --- */
512
- let watchIndex: number = 0;
513
-
514
- /**
515
- * --- 添加 DOM 内容变化监视 ---
516
- * @param el dom 对象
517
- * @param cb 回调
518
- * @param mode 监听模式
519
- * @param taskId 归属到一个任务里可留空,App 模式下无效
520
- */
521
- export function watch(el: HTMLElement, cb: (mutations: MutationRecord[]) => void | Promise<void>, mode: 'child' | 'childsub' | 'style' | 'text' | 'default' = 'default', immediate: boolean = false, taskId?: number): boolean {
522
- if (isWatch(el)) {
523
- return false;
524
- }
525
- if (immediate) {
526
- try {
527
- const r = cb([]);
528
- if (r instanceof Promise) {
529
- r.catch(function(e) {
530
- console.log('dom.watch', e);
531
- });
532
- }
533
- }
534
- catch (e) {
535
- console.log('dom.watch', e);
536
- }
537
- }
538
- const index = watchIndex;
539
- let moi: MutationObserverInit;
540
- switch (mode) {
541
- case 'child': {
542
- moi = {
543
- 'childList': true
544
- };
545
- break;
546
- }
547
- case 'childsub': {
548
- moi = {
549
- 'childList': true,
550
- 'subtree': true
551
- };
552
- break;
553
- }
554
- case 'style': {
555
- moi = {
556
- 'attributeFilter': ['style', 'class'],
557
- 'attributeOldValue': true,
558
- 'attributes': true
559
- };
560
- break;
561
- }
562
- case 'text': {
563
- moi = {
564
- 'characterData': true,
565
- 'childList': true,
566
- 'subtree': true
567
- };
568
- break;
569
- }
570
- default: {
571
- moi = {
572
- 'attributeFilter': ['style', 'class'],
573
- 'attributeOldValue': true,
574
- 'attributes': true,
575
- 'characterData': true,
576
- 'childList': true,
577
- 'subtree': true
578
- };
579
- }
580
- }
581
- const mo = new MutationObserver((mutations) => {
582
- if (!document.body.contains(el)) {
583
- mo.disconnect();
584
- if (watchList[index]) {
585
- delete watchList[index];
586
- }
587
- return;
588
- }
589
- try {
590
- const r = cb(mutations);
591
- if (r instanceof Promise) {
592
- r.catch(function(e) {
593
- console.log('dom.watch', e);
594
- });
595
- }
596
- }
597
- catch (e) {
598
- console.log('dom.watch', e);
599
- }
600
- });
601
- mo.observe(el, moi);
602
- watchList[index] = {
603
- 'el': el,
604
- 'mo': mo,
605
- 'taskId': taskId
606
- };
607
- el.dataset.cgMoindex = index.toString();
608
- ++watchIndex;
609
- return true;
610
- /*
611
- {
612
- 'attributeFilter': ['style', 'class'],
613
- 'attributes': true,
614
- 'characterData': true,
615
- 'childList': true,
616
- 'subtree': true
617
- }
618
- */
619
- }
620
-
621
- /**
622
- * --- 移除监视 Element 对象变动 ---
623
- * @param el 要移除监视
624
- * @param taskId 校验任务 id 可留空,App 模式下无效
625
- */
626
- export function unwatch(el: HTMLElement, taskId?: number): void {
627
- const index = el.dataset.cgMoindex;
628
- if (index === undefined) {
629
- return;
630
- }
631
- const item = watchList[index];
632
- if (taskId && item.taskId !== taskId) {
633
- return;
634
- }
635
- el.removeAttribute('data-cg-moindex');
636
- watchList[index].mo.disconnect();
637
- delete watchList[index];
638
- }
639
-
640
- /**
641
- * --- 检测一个标签是否正在被 watchSize ---
642
- * @param el 要检测的标签
643
- */
644
- export function isWatch(el: HTMLElement): boolean {
645
- return el.dataset.cgMoindex ? true : false;
646
- }
647
-
648
- /**
649
- * --- 清除某个任务下面的所有 watch 监视,App 模式下无效 ---
650
- * @param taskId 任务 id,App 模式下无效
651
- */
652
- export function clearWatch(taskId: number): void {
653
- for (const index in watchList) {
654
- const item = watchList[index];
655
- if (taskId !== item.taskId) {
656
- continue;
657
- }
658
- item.el.removeAttribute('data-cg-moindex');
659
- item.mo.disconnect();
660
- delete watchList[index];
661
- }
662
- }
663
-
664
- // ----------------------
665
- // --- watch cg timer ---
666
- // ----------------------
667
-
668
- // --- watch 和 watchSize 依靠 cg 去清除元素已经消失但还占用 map 的情况 ---
669
- // --- style 和 property 因为是我们自己实现的,随时就能知道元素是否已经被移除了 ---
670
-
671
- const watchCgTimerHandler = function(): void {
672
- for (const index in watchSizeList) {
673
- const item = watchSizeList[index];
674
- if (document.body.contains(item.el)) {
675
- continue;
676
- }
677
- delete watchSizeList[index];
678
- }
679
- for (const index in watchList) {
680
- const item = watchList[index];
681
- if (document.body.contains(item.el)) {
682
- continue;
683
- }
684
- delete watchList[index];
685
- }
686
- window.setTimeout(watchCgTimerHandler, 1000 * 60 * 7);
687
- };
688
- watchCgTimerHandler();
689
-
690
- // ------------------
691
- // --- watchStyle ---
692
- // ------------------
693
-
694
- interface IWatchStyleItem {
695
- 'el': HTMLElement;
696
- 'sd': CSSStyleDeclaration;
697
- 'names': Record<string, {
698
- 'val': string;
699
- 'cb': Array<(name: string, value: string, old: string) => void | Promise<void>>;
700
- }>;
701
- }
702
-
703
- const watchStyleList: Record<
704
- /** --- formId --- */
705
- string,
706
- Record<
707
- /** --- panelId 或 default --- */
708
- string,
709
- Record<
710
- /** --- index 值 --- */
711
- string,
712
- IWatchStyleItem
713
- >
714
- >
715
- > = {};
716
-
717
- /** --- 监视元素的 data-cg-styleindex --- */
718
- let watchStyleIndex: number = 0;
719
-
720
- /**
721
- * --- 监听一个标签的计算后样式的变化 ---
722
- * @param el 对象
723
- * @param name 样式名
724
- * @param cb 变更回调
725
- * @param immediate 是否立刻执行一次
726
- */
727
- export function watchStyle(
728
- el: HTMLElement,
729
- name: string | string[],
730
- cb: (name: string, value: string, old: string) => void | Promise<void>,
731
- immediate: boolean = false
732
- ): void {
733
- if (typeof name === 'string') {
734
- name = [name];
735
- }
736
- // --- 获取监视标签的所属 wrap ---
737
- const formWrap = findParentByData(el, 'form-id');
738
- if (!formWrap) {
739
- return;
740
- }
741
- const formId = formWrap.dataset.formId!;
742
- // --- 获取监视标签的所属 panel ---
743
- const panelWrap = findParentByData(el, 'panel-id');
744
- const panelId = panelWrap ? panelWrap.dataset.panelId! : 'default';
745
- /** --- 监视 index 值 --- */
746
- const index = el.dataset.cgStyleindex;
747
- if (index) {
748
- // --- 已经有监听了 ---
749
- const item = watchStyleList[formId][panelId][index];
750
- for (const n of name) {
751
- if (!item.names[n]) {
752
- item.names[n] = {
753
- 'val': (item.sd as any)[n],
754
- 'cb': [cb]
755
- };
756
- }
757
- else {
758
- item.names[n].cb.push(cb);
759
- }
760
- if (immediate) {
761
- cb(n, (item.sd as any)[n], '') as any;
762
- }
763
- }
764
- return;
765
- }
766
- // --- 创建 object ---
767
- if (!watchStyleList[formId]) {
768
- watchStyleList[formId] = {};
769
- }
770
- if (!watchStyleList[formId][panelId]) {
771
- watchStyleList[formId][panelId] = {};
772
- }
773
- // --- 创建监听 ---
774
- const sd = getComputedStyle(el);
775
- watchStyleList[formId][panelId][watchStyleIndex] = {
776
- 'el': el,
777
- 'sd': sd,
778
- 'names': {}
779
- };
780
- const item = watchStyleList[formId][panelId][watchStyleIndex];
781
- for (const n of name) {
782
- item.names[n] = {
783
- 'val': (item.sd as any)[n],
784
- 'cb': [cb]
785
- };
786
- if (immediate) {
787
- cb(n, (item.sd as any)[n], '') as any;
788
- }
789
- }
790
- el.dataset.cgStyleindex = watchStyleIndex.toString();
791
- ++watchStyleIndex;
792
- }
793
-
794
- /**
795
- * --- 检测一个标签是否正在被 watchStyle ---
796
- * @param el 要检测的标签
797
- */
798
- export function isWatchStyle(el: HTMLElement): boolean {
799
- return el.dataset.cgStyleindex ? true : false;
800
- }
801
-
802
- /**
803
- * --- 清除某个窗体的所有 watch style 监视,App 模式下无效 ---
804
- * @param formId 窗体 id
805
- * @param panelId 若指定则只清除当前窗体的某个 panel 的 watch
806
- */
807
- export function clearWatchStyle(formId: number | string, panelId?: number): void {
808
- if (!watchStyleList[formId]) {
809
- return;
810
- }
811
- for (const panel in watchStyleList[formId]) {
812
- if (panelId) {
813
- if (panel !== panelId.toString()) {
814
- continue;
815
- }
816
- }
817
- for (const index in watchStyleList[formId][panel]) {
818
- const item = watchStyleList[formId][panel][index];
819
- item.el.removeAttribute('data-cg-styleindex');
820
- }
821
- delete watchStyleList[formId][panel];
822
- }
823
- if (Object.keys(watchStyleList[formId]).length) {
824
- return;
825
- }
826
- delete watchStyleList[formId];
827
- }
828
-
829
- // ---------------------
830
- // --- watchProperty ---
831
- // ---------------------
832
-
833
- interface IWatchPropertyItem {
834
- 'el': HTMLElement;
835
- 'names': Record<string, {
836
- 'val': string;
837
- 'cb': Array<(name: string, value: string) => void | Promise<void>>;
838
- }>;
839
- }
840
-
841
- /**
842
- * --- 监听中的标签对象,对应 formId -> 数组列表 ---
843
- */
844
- const watchPropertyObjects: Record<
845
- /** --- formId --- */
846
- string,
847
- Record<
848
- /** --- panelId 或 default --- */
849
- string,
850
- Record<
851
- /** --- index 值 --- */
852
- string,
853
- IWatchPropertyItem
854
- >
855
- >
856
- > = {};
857
-
858
- /** --- 监视元素的 data-cg-propertyindex --- */
859
- let watchPropertyIndex: number = 0;
860
-
861
- /**
862
- * --- 监听一个对象的属性变化 ---
863
- * @param el 对象
864
- * @param name 属性名
865
- * @param cb 回调函数
866
- * @param immediate 是否立即执行一次
867
- */
868
- export function watchProperty(
869
- el: HTMLElement,
870
- name: string | string[],
871
- cb: (name: string, value: any) => void | Promise<void>,
872
- immediate: boolean = false
873
- ): void {
874
- if (typeof name === 'string') {
875
- name = [name];
876
- }
877
- // --- 获取监视标签的所属 wrap ---
878
- const formWrap = findParentByData(el, 'form-id');
879
- if (!formWrap) {
880
- return;
881
- }
882
- const formId = formWrap.dataset.formId!;
883
- // --- 获取监视标签的所属 panel ---
884
- const panelWrap = findParentByData(el, 'panel-id');
885
- const panelId = panelWrap ? panelWrap.dataset.panelId! : 'default';
886
- /** --- 监视 index 值 --- */
887
- const index = el.dataset.cgPropertyindex;
888
- if (index) {
889
- // --- 已经有监听了 ---
890
- const item = watchPropertyObjects[formId][panelId][index];
891
- for (const n of name) {
892
- if (!item.names[n]) {
893
- item.names[n] = {
894
- 'val': (item.el as any)[n],
895
- 'cb': [cb]
896
- };
897
- }
898
- else {
899
- item.names[n].cb.push(cb);
900
- }
901
- if (immediate) {
902
- cb(n, (item.el as any)[n]) as any;
903
- }
904
- }
905
- return;
906
- }
907
- // --- 创建 object ---
908
- if (!watchPropertyObjects[formId]) {
909
- watchPropertyObjects[formId] = {};
910
- }
911
- if (!watchPropertyObjects[formId][panelId]) {
912
- watchPropertyObjects[formId][panelId] = {};
913
- }
914
- // --- 创建监听 ---
915
- watchPropertyObjects[formId][panelId][watchPropertyIndex] = {
916
- 'el': el,
917
- 'names': {}
918
- };
919
- const item = watchPropertyObjects[formId][panelId][watchPropertyIndex];
920
- for (const n of name) {
921
- item.names[n] = {
922
- 'val': (item.el as any)[n],
923
- 'cb': [cb]
924
- };
925
- if (immediate) {
926
- cb(n, (item.el as any)[n]) as any;
927
- }
928
- }
929
- el.dataset.cgPropertyindex = watchPropertyIndex.toString();
930
- ++watchPropertyIndex;
931
- }
932
-
933
- /**
934
- * --- 检测一个标签是否正在被 watchProperty ---
935
- * @param el 要检测的标签
936
- */
937
- export function isWatchProperty(el: HTMLElement): boolean {
938
- return el.dataset.cgPropertyindex ? true : false;
939
- }
940
-
941
- /**
942
- * --- 清除某个窗体的所有 watch property 监视,虽然窗体结束后相关监视永远不会再被执行,但是会形成冗余,App 模式下无效 ---
943
- * @param formId 窗体 id
944
- * @param panelId 若指定则只清除当前窗体的某个 panel 的 watch
945
- */
946
- export function clearWatchProperty(formId: number | string, panelId?: number): void {
947
- if (!watchPropertyObjects[formId]) {
948
- return;
949
- }
950
- for (const panel in watchPropertyObjects[formId]) {
951
- if (panelId) {
952
- if (panel !== panelId.toString()) {
953
- continue;
954
- }
955
- }
956
- for (const index in watchPropertyObjects[formId][panel]) {
957
- const item = watchPropertyObjects[formId][panel][index];
958
- item.el.removeAttribute('data-cg-propertyindex');
959
- }
960
- delete watchPropertyObjects[formId][panel];
961
- }
962
- if (Object.keys(watchPropertyObjects[formId]).length) {
963
- return;
964
- }
965
- delete watchPropertyObjects[formId];
966
- }
967
-
968
- // -------------------
969
- // --- watch timer ---
970
- // -------------------
971
-
972
- export function getWatchInfo(): types.IGetWatchInfoResult {
973
- const rtn: types.IGetWatchInfoResult = {
974
- 'formId': 0,
975
- 'default': {},
976
- 'panels': {}
977
- };
978
- const formId: number | null = form.getFocus();
979
- if (!formId) {
980
- return rtn;
981
- }
982
- rtn.formId = formId;
983
- const panelIds = form.getActivePanel(formId);
984
- const handler = (item: {
985
- 'el': HTMLElement;
986
- 'names'?: Record<string, any>;
987
- }, type: 'style' | 'property' | 'position', panelId?: string): void => {
988
- if (panelId) {
989
- if (!rtn.panels[panelId]) {
990
- rtn.panels[panelId] = {};
991
- }
992
- }
993
- const ritem = panelId ? rtn.panels[panelId] : rtn.default;
994
- /** --- 控件名 --- */
995
- const cname = item.el.dataset.cgControl ?? findParentByData(item.el, 'cg-control')?.dataset.cgControl ?? 'unknown';
996
- if (!ritem[cname]) {
997
- ritem[cname] = {
998
- 'style': {
999
- 'count': 0,
1000
- 'list': []
1001
- },
1002
- 'property': {
1003
- 'count': 0,
1004
- 'list': []
1005
- },
1006
- 'position': {
1007
- 'count': 0
1008
- }
1009
- };
1010
- }
1011
- ++ritem[cname][type].count;
1012
- if (item.names && type !== 'position') {
1013
- for (const name in item.names) {
1014
- if (ritem[cname][type].list.includes(name)) {
1015
- continue;
1016
- }
1017
- ritem[cname][type].list.push(name);
1018
- }
1019
- }
1020
- };
1021
- // --- 先执行窗体默认的 ---
1022
- if (watchStyleList[formId]) {
1023
- if (watchStyleList[formId].default) {
1024
- for (const index in watchStyleList[formId].default) {
1025
- handler(watchStyleList[formId].default[index], 'style');
1026
- }
1027
- }
1028
- // --- 再执行活跃的 panel 的 ---
1029
- for (const id of panelIds) {
1030
- if (watchStyleList[formId][id]) {
1031
- for (const index in watchStyleList[formId][id]) {
1032
- handler(watchStyleList[formId][id][index], 'style', id.toString());
1033
- }
1034
- }
1035
- }
1036
- }
1037
- // --- 先执行窗体默认的 ---
1038
- if (watchPropertyObjects[formId]) {
1039
- if (watchPropertyObjects[formId].default) {
1040
- for (const index in watchPropertyObjects[formId].default) {
1041
- handler(watchPropertyObjects[formId].default[index], 'property');
1042
- }
1043
- }
1044
- // --- 再执行活跃的 panel 的 ---
1045
- for (const id of panelIds) {
1046
- if (watchPropertyObjects[formId]?.[id]) {
1047
- for (const index in watchPropertyObjects[formId][id]) {
1048
- handler(watchPropertyObjects[formId][id][index], 'property', id.toString());
1049
- }
1050
- }
1051
- }
1052
- }
1053
- // --- 先执行窗体默认的 ---
1054
- if (watchPositionObjects[formId]) {
1055
- if (watchPositionObjects[formId].default) {
1056
- for (const index in watchPositionObjects[formId].default) {
1057
- handler(watchPositionObjects[formId].default[index], 'position');
1058
- }
1059
- }
1060
- // --- 再执行活跃的 panel 的 ---
1061
- for (const id of panelIds) {
1062
- if (watchPositionObjects[formId]?.[id]) {
1063
- for (const index in watchPositionObjects[formId][id]) {
1064
- handler(watchPositionObjects[formId][id][index], 'position', id.toString());
1065
- }
1066
- }
1067
- }
1068
- }
1069
- return rtn;
1070
- }
1071
-
1072
- /** --- watch style 的 timer --- */
1073
- let watchTimer = 0;
1074
- const watchTimerHandler = function(): void {
1075
- // --- 为什么要判断 form.getFocus 存在否,因为 form 类可能还没加载出来,这个函数就已经开始执行了 ---
1076
- if (form.getFocus) {
1077
- /** --- --- */
1078
- const formId: number | null = form.getFocus();
1079
- if (formId) {
1080
- /** --- 活跃的 panel --- */
1081
- const panelIds = form.getActivePanel(formId);
1082
- if (watchStyleList[formId]) {
1083
- // --- style ---
1084
- const handler = (item: IWatchStyleItem, panelId: string, index: string): void => {
1085
- if (!document.body.contains(item.el)) {
1086
- delete watchStyleList[formId][panelId][index];
1087
- if (!Object.keys(watchStyleList[formId][panelId]).length) {
1088
- delete watchStyleList[formId][panelId];
1089
- }
1090
- if (!Object.keys(watchStyleList[formId]).length) {
1091
- delete watchStyleList[formId];
1092
- }
1093
- return;
1094
- }
1095
- // --- 执行 cb ---
1096
- for (const name in item.names) {
1097
- if ((item.sd as any)[name] === item.names[name].val) {
1098
- continue;
1099
- }
1100
- const old = item.names[name].val;
1101
- item.names[name].val = (item.sd as any)[name];
1102
- for (const cb of item.names[name].cb) {
1103
- cb(name, (item.sd as any)[name], old) as any;
1104
- }
1105
- }
1106
- };
1107
- // --- 先执行窗体默认的 ---
1108
- if (watchStyleList[formId].default) {
1109
- for (const index in watchStyleList[formId].default) {
1110
- handler(watchStyleList[formId].default[index], 'default', index);
1111
- }
1112
- }
1113
- // --- 再执行活跃的 panel 的 ---
1114
- for (const id of panelIds) {
1115
- if (watchStyleList[formId][id]) {
1116
- for (const index in watchStyleList[formId][id]) {
1117
- handler(watchStyleList[formId][id][index], id.toString(), index);
1118
- }
1119
- }
1120
- }
1121
- }
1122
- // --- property ---
1123
- if (watchPropertyObjects[formId]) {
1124
- // --- property ---
1125
- const handler = (item: IWatchPropertyItem, panelId: string, index: string): void => {
1126
- if (!document.body.contains(item.el)) {
1127
- delete watchPropertyObjects[formId][panelId][index];
1128
- if (!Object.keys(watchPropertyObjects[formId][panelId]).length) {
1129
- delete watchPropertyObjects[formId][panelId];
1130
- }
1131
- if (!Object.keys(watchPropertyObjects[formId]).length) {
1132
- delete watchPropertyObjects[formId];
1133
- }
1134
- return;
1135
- }
1136
- // --- 执行 cb ---
1137
- for (const name in item.names) {
1138
- if ((item.el as any)[name] === item.names[name].val) {
1139
- continue;
1140
- }
1141
- item.names[name].val = (item.el as any)[name];
1142
- for (const cb of item.names[name].cb) {
1143
- cb(name, (item.el as any)[name]) as any;
1144
- }
1145
- }
1146
- };
1147
- // --- 先执行窗体默认的 ---
1148
- if (watchPropertyObjects[formId].default) {
1149
- for (const index in watchPropertyObjects[formId].default) {
1150
- handler(watchPropertyObjects[formId].default[index], 'default', index);
1151
- }
1152
- }
1153
- // --- 再执行活跃的 panel 的 ---
1154
- for (const id of panelIds) {
1155
- if (watchPropertyObjects[formId][id]) {
1156
- for (const index in watchPropertyObjects[formId][id]) {
1157
- handler(watchPropertyObjects[formId][id][index], id.toString(), index);
1158
- }
1159
- }
1160
- }
1161
- }
1162
- // --- position ---
1163
- if (watchPositionObjects[formId]) {
1164
- // --- position ---
1165
- const handler = (item: types.IWatchPositionItem, panelId: string, index: string): void => {
1166
- if (!document.body.contains(item.el)) {
1167
- delete watchPositionObjects[formId][panelId][index];
1168
- if (!Object.keys(watchPositionObjects[formId][panelId]).length) {
1169
- delete watchPositionObjects[formId][panelId];
1170
- }
1171
- if (!Object.keys(watchPositionObjects[formId]).length) {
1172
- delete watchPositionObjects[formId];
1173
- }
1174
- return;
1175
- }
1176
- // --- 执行 cb ---
1177
- const rect = item.el.getBoundingClientRect();
1178
- let position = false;
1179
- let size = false;
1180
- if (item.rect.left !== rect.left || item.rect.top !== rect.top) {
1181
- position = true;
1182
- }
1183
- if (item.rect.width !== rect.width || item.rect.height !== rect.height) {
1184
- size = true;
1185
- }
1186
- if (position || size) {
1187
- item.handler({
1188
- 'position': position,
1189
- 'size': size
1190
- }) as any;
1191
- }
1192
- watchPositionObjects[formId][panelId][index].rect = rect;
1193
- };
1194
- // --- 先执行窗体默认的 ---
1195
- if (watchPositionObjects[formId].default) {
1196
- for (const index in watchPositionObjects[formId].default) {
1197
- handler(watchPositionObjects[formId].default[index], 'default', index);
1198
- }
1199
- }
1200
- // --- 再执行活跃的 panel 的 ---
1201
- for (const id of panelIds) {
1202
- if (watchPositionObjects[formId][id]) {
1203
- for (const index in watchPositionObjects[formId][id]) {
1204
- handler(watchPositionObjects[formId][id][index], id.toString(), index);
1205
- }
1206
- }
1207
- }
1208
- }
1209
- }
1210
- }
1211
- watchTimer = requestAnimationFrame(watchTimerHandler);
1212
- };
1213
- watchTimerHandler();
1214
-
1215
- /**
1216
- * --- 鼠标/手指没移动时,click 才生效 ---
1217
- * @param e 事件对象
1218
- * @param handler 回调
1219
- */
1220
- export function bindClick(
1221
- e: MouseEvent | TouchEvent,
1222
- handler: (e: MouseEvent | TouchEvent, x: number, y: number) => void | Promise<void>
1223
- ): void {
1224
- if ((e instanceof MouseEvent) && (e.button > 0)) {
1225
- return;
1226
- }
1227
- const x = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
1228
- const y = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
1229
- const time = Date.now();
1230
- bindDown(e, {
1231
- up: (ne) => {
1232
- if (Date.now() - time >= 250) {
1233
- return;
1234
- }
1235
- const nx = ne instanceof MouseEvent ? ne.clientX : ne.changedTouches[0].clientX;
1236
- const ny = ne instanceof MouseEvent ? ne.clientY : ne.changedTouches[0].clientY;
1237
- if (nx === x && ny === y) {
1238
- handler(ne, nx, ny) as any;
1239
- }
1240
- }
1241
- });
1242
- }
1243
-
1244
- /** --- 双击事件中,最后一次单击的数据 --- */
1245
- const lastDblClickData = {
1246
- 'time': 0,
1247
- 'x': 0,
1248
- 'y': 0
1249
- };
1250
-
1251
- /**
1252
- * --- 相当于鼠标/手指两次 click 的效果,并且两次位置差别不太大,dblclick 才生效 ---
1253
- * @param e 事件对象
1254
- * @param handler 回调
1255
- */
1256
- export function bindDblClick(
1257
- e: MouseEvent | TouchEvent,
1258
- handler: (e: MouseEvent | TouchEvent, x: number, y: number) => void | Promise<void>
1259
- ): void {
1260
- bindClick(e, (ne, x, y) => {
1261
- // --- 判断当前第几次点击 ---
1262
- const now = Date.now();
1263
- if (now - lastDblClickData.time <= 300) {
1264
- const xx = Math.abs(x - lastDblClickData.x);
1265
- const xy = Math.abs(y - lastDblClickData.y);
1266
- if (xx < 10 && xy < 10) {
1267
- // --- 响应双击 ---
1268
- handler(ne, x, y) as any;
1269
- lastDblClickData.time = 0;
1270
- lastDblClickData.x = 0;
1271
- lastDblClickData.y = 0;
1272
- return;
1273
- }
1274
- }
1275
- lastDblClickData.time = now;
1276
- lastDblClickData.x = x;
1277
- lastDblClickData.y = y;
1278
- });
1279
- }
1280
-
1281
- /**
1282
- * --- 绑定按下以及弹起事件,touch 和 mouse 只会绑定一个 ---
1283
- * @param oe MouseEvent | TouchEvent
1284
- * @param opt 回调选项
1285
- */
1286
- export function bindDown<T extends MouseEvent | TouchEvent>(oe: T, opt: types.IBindDownOptions<T>): void {
1287
- if (hasTouchButMouse(oe)) {
1288
- return;
1289
- }
1290
- /** --- 上一次的坐标 --- */
1291
- let ox: number, oy: number;
1292
- if (oe instanceof MouseEvent) {
1293
- ox = oe.clientX;
1294
- oy = oe.clientY;
1295
- }
1296
- else {
1297
- ox = oe.touches[0].clientX;
1298
- oy = oe.touches[0].clientY;
1299
- }
1300
-
1301
- /** --- 是否是第一次执行 move --- */
1302
- let isStart: boolean = false;
1303
-
1304
- let end: (<TU extends T>(e: TU) => void) | undefined = undefined;
1305
- const move = function<TU extends T>(e: TU): void {
1306
- // --- 虽然上层已经有 preventDefault 了,但是有可能 e.target 会被注销,这样就响应不到上层的 preventDefault 事件,所以要在这里再加一个 ---
1307
- if (!e.target || !document.body.contains(e.target as HTMLElement) && e.cancelable) {
1308
- e.preventDefault();
1309
- }
1310
- /** --- 本次的移动方向 --- */
1311
- let dir: 'top' | 'right' | 'bottom' | 'left' = 'top';
1312
- const x: number = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
1313
- const y: number = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
1314
- if (x === ox && y === oy) {
1315
- return;
1316
- }
1317
- const xx = x - ox;
1318
- const xy = y - oy;
1319
- if (Math.abs(xy) > Math.abs(xx)) {
1320
- // --- 竖向滚动 ---
1321
- if (xy < 0) {
1322
- // -- 向上移 ---
1323
- dir = 'top';
1324
- }
1325
- else {
1326
- // -- 向下移 ---
1327
- dir = 'bottom';
1328
- }
1329
- }
1330
- else {
1331
- // --- 横向滚动 ---
1332
- if (xx < 0) {
1333
- // -- 向左移 ---
1334
- dir = 'left';
1335
- }
1336
- else {
1337
- // -- 向右移 ---
1338
- dir = 'right';
1339
- }
1340
- }
1341
- ox = x;
1342
- oy = y;
1343
-
1344
- if (!isStart) {
1345
- isStart = true;
1346
- if (opt.start && (opt.start(e) === false)) {
1347
- if (e instanceof MouseEvent) {
1348
- window.removeEventListener('mousemove', move as EventListener);
1349
- window.removeEventListener('mouseup', end as EventListener);
1350
- }
1351
- else {
1352
- (oe.target as HTMLElement).removeEventListener('touchmove', move as EventListener);
1353
- (oe.target as HTMLElement).removeEventListener('touchend', end as EventListener);
1354
- (oe.target as HTMLElement).removeEventListener('touchcancel', end as EventListener);
1355
- }
1356
- return;
1357
- }
1358
- }
1359
- if (opt.move && (opt.move(e, dir) === false)) {
1360
- if (e instanceof MouseEvent) {
1361
- window.removeEventListener('mousemove', move as EventListener);
1362
- window.removeEventListener('mouseup', end as EventListener);
1363
- }
1364
- else {
1365
- if (oe.target) {
1366
- (oe.target as HTMLElement).removeEventListener('touchmove', move as EventListener);
1367
- (oe.target as HTMLElement).removeEventListener('touchend', end as EventListener);
1368
- (oe.target as HTMLElement).removeEventListener('touchcancel', end as EventListener);
1369
- }
1370
- }
1371
- return;
1372
- }
1373
- };
1374
- end = function<TU extends T>(e: TU): void {
1375
- if (e instanceof MouseEvent) {
1376
- window.removeEventListener('mousemove', move as EventListener);
1377
- window.removeEventListener('mouseup', end as EventListener);
1378
- }
1379
- else {
1380
- if (oe.target) {
1381
- (oe.target as HTMLElement).removeEventListener('touchmove', move as EventListener);
1382
- (oe.target as HTMLElement).removeEventListener('touchend', end as EventListener);
1383
- (oe.target as HTMLElement).removeEventListener('touchcancel', end as EventListener);
1384
- }
1385
- }
1386
- opt.up?.(e) as any;
1387
- if (isStart) {
1388
- opt.end?.(e) as any;
1389
- }
1390
- };
1391
- if (oe instanceof MouseEvent) {
1392
- window.addEventListener('mousemove', move as (e: MouseEvent) => void, {
1393
- 'passive': false
1394
- });
1395
- window.addEventListener('mouseup', end as (e: MouseEvent) => void);
1396
- }
1397
- else {
1398
- (oe.target as HTMLElement).addEventListener('touchmove', move as (e: TouchEvent) => void, {
1399
- 'passive': false
1400
- });
1401
- (oe.target as HTMLElement).addEventListener('touchend', end as (e: TouchEvent) => void);
1402
- (oe.target as HTMLElement).addEventListener('touchcancel', end as (e: TouchEvent) => void);
1403
- }
1404
- opt.down?.(oe);
1405
- }
1406
-
1407
- /**
1408
- * --- 绑定缩放,要绑定到 mousedown、touchstart、touchmove、touchend、wheel 上 ---
1409
- * @param oe 触发的时间
1410
- * @param handler 回调函数
1411
- */
1412
- export function bindScale(oe: MouseEvent | TouchEvent | WheelEvent, handler: (e: MouseEvent | TouchEvent | WheelEvent, scale: number, cpos: { 'x': number; 'y': number; }) => void | Promise<void>): void {
1413
- const el = oe.currentTarget as HTMLElement;
1414
- if (!el) {
1415
- return;
1416
- }
1417
- if (oe instanceof TouchEvent) {
1418
- // --- 指头 ---
1419
- if (oe.type === 'touchend') {
1420
- if (oe.touches.length) {
1421
- return;
1422
- }
1423
- el.removeAttribute('data-cg-scale');
1424
- return;
1425
- }
1426
- /** --- 本次 x 坐标点 --- */
1427
- const ex = [oe.touches[0].clientX, oe.touches[1]?.clientX ?? -1000];
1428
- /** --- 本次 y 坐标点 --- */
1429
- const ey = [oe.touches[0].clientY, oe.touches[1]?.clientY ?? -1000];
1430
- /** --- 当前两指间的距离 --- */
1431
- let ndis = 0;
1432
- /** --- 当前中心点 --- */
1433
- const epos = {
1434
- 'x': ex[0],
1435
- 'y': ey[0]
1436
- };
1437
- if (ex[1] !== -1000) {
1438
- // --- 计算两指间距离 ---
1439
- const nx = ex[0] - ex[1];
1440
- const ny = ey[0] - ey[1];
1441
- ndis = Math.hypot(nx, ny);
1442
- // --- 计算中心点 ---
1443
- const cnx = (ex[0] + ex[1]) / 2;
1444
- const cny = (ey[0] + ey[1]) / 2;
1445
- epos['x'] = cnx;
1446
- epos['y'] = cny;
1447
- }
1448
- if (el.dataset.cgScale === undefined) {
1449
- // --- 首次 ---
1450
- el.dataset.cgScale = JSON.stringify({
1451
- /** --- 上次两指间距离 --- */
1452
- 'dis': ndis,
1453
- /** --- 上次 x 坐标点 --- */
1454
- 'x': ex,
1455
- /** --- 上次 y 坐标点 --- */
1456
- 'y': ey,
1457
- /** --- 上次的中心点 --- */
1458
- 'pos': epos
1459
- });
1460
- return;
1461
- }
1462
- const d = JSON.parse(el.dataset.cgScale);
1463
- // --- 看看是否计算偏移 ---
1464
- let notchange = false;
1465
- if (ex[1] !== -1000) {
1466
- if (d.x[1] === -1000) {
1467
- notchange = true;
1468
- }
1469
- }
1470
- else {
1471
- if (d.x[1] !== -1000) {
1472
- notchange = true;
1473
- }
1474
- }
1475
- // --- 计算缩放偏移 ---
1476
- const scale = ndis > 0 && d.dis > 0 ? ndis / d.dis : 1;
1477
- handler(oe, scale, {
1478
- 'x': notchange ? 0 : epos['x'] - d.pos['x'],
1479
- 'y': notchange ? 0 : epos['y'] - d.pos['y']
1480
- }) as any;
1481
- // --- 覆盖 ---
1482
- el.dataset.cgScale = JSON.stringify({
1483
- 'dis': ndis,
1484
- 'x': ex,
1485
- 'y': ey,
1486
- 'pos': epos
1487
- });
1488
- return;
1489
- }
1490
- if (oe instanceof WheelEvent) {
1491
- // --- 滚轮 ---
1492
- if (!oe.deltaY) {
1493
- return;
1494
- }
1495
- /** --- 本次数值 --- */
1496
- const delta = Math.abs(oe.deltaY);
1497
- /** --- 缩放因子 --- */
1498
- const zoomFactor = delta * (delta > 50 ? 0.0015 : 0.003);
1499
- handler(oe, oe.deltaY < 0 ? 1 + zoomFactor : 1 - zoomFactor, {
1500
- 'x': 0,
1501
- 'y': 0
1502
- }) as any;
1503
- return;
1504
- }
1505
- // --- 纯鼠标拖动 ---
1506
- bindMove(oe, {
1507
- 'move': (e, opt) => {
1508
- handler(oe, 1, {
1509
- 'x': opt.ox,
1510
- 'y': opt.oy
1511
- }) as any;
1512
- }
1513
- });
1514
- }
1515
-
1516
- /** --- 绑定拖拉响应操作的 wheel 数据对象 --- */
1517
- const gestureWheel = {
1518
- /** --- 最后一次响应事件时间 --- */
1519
- 'last': 0,
1520
- /** --- 本轮 offset 滚动距离 --- */
1521
- 'offset': 0,
1522
- /** --- 本轮是否已经完成了滚动 --- */
1523
- 'done': false,
1524
- /** --- 未执行等待隐藏的 timer --- */
1525
- 'timer': 0,
1526
- /** --- 是否等待首次执行中 --- */
1527
- 'firstTimer': false,
1528
- /** --- 本轮滚动的方向 --- */
1529
- 'dir': ''
1530
- };
1531
-
1532
- /**
1533
- * --- 绑定上拉、下拉、左拉、右拉 ---
1534
- * @param oe 响应事件
1535
- * @param before before 事件,返回 1 则显示 gesture,0 则不处理(可能会向上传递事件),-1 则 stopPropagation(本层可拖动,若实际不可拖动则可能导致浏览器页面滚动)
1536
- * @param handler 执行完毕的话才会回调
1537
- */
1538
- export function bindGesture(oe: MouseEvent | TouchEvent | WheelEvent, before: (e: MouseEvent | TouchEvent | WheelEvent, dir: 'top' | 'right' | 'bottom' | 'left') => number, handler?: (dir: 'top' | 'right' | 'bottom' | 'left') => void | Promise<void>): void {
1539
- const el = oe.currentTarget as HTMLElement | null;
1540
- if (!el) {
1541
- return;
1542
- }
1543
- const rect = el.getBoundingClientRect();
1544
- if ((oe instanceof MouseEvent || oe instanceof TouchEvent) && !(oe instanceof WheelEvent)) {
1545
- // --- touch / mouse 触发的,dir 会和鼠标的 dir 相反,向下拖动是上方加载 ---
1546
- let offset: number = 0;
1547
- let origin: number = 0;
1548
- /** --- 是否第一次执行 move 事件,1-是,0-不是且继续,-1-终止 --- */
1549
- let first = 1;
1550
- /** --- 哪个方向要加载 --- */
1551
- let dir: 'top' | 'right' | 'bottom' | 'left' = 'top';
1552
- /** --- 当前手势方向 --- */
1553
- bindDown(oe, {
1554
- move: (e, d) => {
1555
- if (first < 0) {
1556
- if (first > -30) {
1557
- const rtn = before(e, dir);
1558
- if (rtn === 1) {
1559
- e.stopPropagation();
1560
- e.preventDefault();
1561
- }
1562
- else if (rtn === -1) {
1563
- e.stopPropagation();
1564
- }
1565
- --first;
1566
- }
1567
- return;
1568
- }
1569
- if (first === 1) {
1570
- // --- 第一次执行,响应 before 判断是否继续执行 ---
1571
- first = 0;
1572
- switch (d) {
1573
- case 'top': {
1574
- dir = 'bottom';
1575
- break;
1576
- }
1577
- case 'bottom': {
1578
- dir = 'top';
1579
- break;
1580
- }
1581
- case 'left': {
1582
- dir = 'right';
1583
- break;
1584
- }
1585
- default: {
1586
- dir = 'left';
1587
- }
1588
- }
1589
- const rtn = before(e, dir);
1590
- if (rtn === 1) {
1591
- e.stopPropagation();
1592
- e.preventDefault();
1593
- }
1594
- else {
1595
- if (rtn === -1) {
1596
- e.stopPropagation();
1597
- }
1598
- first = -1;
1599
- return;
1600
- }
1601
- // --- 初始化原点 ---
1602
- switch (dir) {
1603
- case 'top':
1604
- case 'bottom': {
1605
- origin = oe instanceof MouseEvent ? oe.clientY : oe.touches[0].clientY;
1606
- break;
1607
- }
1608
- default: {
1609
- origin = oe instanceof MouseEvent ? oe.clientX : oe.touches[0].clientX;
1610
- }
1611
- }
1612
- }
1613
- /** --- 当前坐标 --- */
1614
- let pos: number = 0;
1615
- switch (dir) {
1616
- case 'top':
1617
- case 'bottom': {
1618
- pos = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
1619
- if (dir === 'top') {
1620
- offset = pos - origin;
1621
- }
1622
- else {
1623
- offset = origin - pos;
1624
- }
1625
- break;
1626
- }
1627
- default: {
1628
- pos = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
1629
- if (dir === 'left') {
1630
- offset = pos - origin;
1631
- }
1632
- else {
1633
- offset = origin - pos;
1634
- }
1635
- }
1636
- }
1637
-
1638
- // --- 处理差值 ---
1639
- if (offset >= 90) {
1640
- offset = 90;
1641
- form.elements.gesture.style.opacity = '1';
1642
- form.elements.gesture.classList.add('done');
1643
- }
1644
- else {
1645
- form.elements.gesture.classList.remove('done');
1646
- if (offset < 0) {
1647
- offset = 0;
1648
- form.elements.gesture.style.opacity = '0';
1649
- }
1650
- else {
1651
- form.elements.gesture.style.opacity = '1';
1652
- }
1653
- }
1654
- form.elements.gesture.style.transform = 'scale(' + (offset / 90).toString() + ')';
1655
-
1656
- // --- 处理标签位置 ---
1657
- switch (dir) {
1658
- case 'top':
1659
- case 'bottom': {
1660
- form.elements.gesture.style.left = (rect.left + ((rect.width - 20) / 2)).toString() + 'px';
1661
- if (dir === 'top') {
1662
- form.elements.gesture.style.top = (rect.top + (offset / 1.5)).toString() + 'px';
1663
- }
1664
- else {
1665
- form.elements.gesture.style.top = (rect.bottom - 20 - (offset / 1.5)).toString() + 'px';
1666
- }
1667
- break;
1668
- }
1669
- default: {
1670
- form.elements.gesture.style.top = (rect.top + ((rect.height - 20) / 2)).toString() + 'px';
1671
- if (dir === 'left') {
1672
- form.elements.gesture.style.left = (rect.left + (offset / 1.5)).toString() + 'px';
1673
- }
1674
- else {
1675
- form.elements.gesture.style.left = (rect.right - 20 - (offset / 1.5)).toString() + 'px';
1676
- }
1677
- }
1678
- }
1679
- },
1680
- end: (): void => {
1681
- form.elements.gesture.style.opacity = '0';
1682
- if (offset < 90) {
1683
- return;
1684
- }
1685
- handler?.(dir) as any;
1686
- }
1687
- });
1688
- }
1689
- else {
1690
- // --- wheel 触发 ---
1691
- (async () => {
1692
- const now = Date.now();
1693
- if (now - gestureWheel.last > 250) {
1694
- // --- 超过 250 毫秒,已经进入下一轮 ---
1695
- gestureWheel.offset = 0;
1696
- gestureWheel.done = false;
1697
- gestureWheel.timer = 0;
1698
- gestureWheel.firstTimer = false;
1699
- gestureWheel.dir = '';
1700
- }
1701
- // --- 赋值最后执行时间 ---
1702
- gestureWheel.last = now;
1703
- if (gestureWheel.firstTimer) {
1704
- return;
1705
- }
1706
- // --- 判断本轮是否已经处理结束 ---
1707
- if (gestureWheel.done) {
1708
- return;
1709
- }
1710
- // --- 获取滚动 delta 坐标 ---
1711
- let deltaY = oe.deltaY;
1712
- let deltaX = oe.deltaX;
1713
- if (clickgo.dom.is.shift) {
1714
- deltaY = oe.deltaX;
1715
- deltaX = oe.deltaY;
1716
- }
1717
- // --- 判断是不是本轮首次滚动 ---
1718
- if (gestureWheel.dir === '') {
1719
- // --- 判断滚动方向 ---
1720
- if (Math.abs(deltaY) > Math.abs(deltaX)) {
1721
- // --- 竖向滚动 ---
1722
- if (deltaY < 0) {
1723
- // --- 向上滚 ---
1724
- gestureWheel.dir = 'top';
1725
- }
1726
- else {
1727
- // --- 向下滚 ---
1728
- gestureWheel.dir = 'bottom';
1729
- }
1730
- }
1731
- else {
1732
- // --- 横向滚动 ---
1733
- if (deltaX < 0) {
1734
- // --- 向左滚 ---
1735
- gestureWheel.dir = 'left';
1736
- }
1737
- else {
1738
- // --- 向下滚 ---
1739
- gestureWheel.dir = 'right';
1740
- }
1741
- }
1742
- // --- 判断是否要显示 gesture ---
1743
- const rtn = before(oe, gestureWheel.dir as any);
1744
- if (rtn === 1) {
1745
- oe.stopPropagation();
1746
- oe.preventDefault();
1747
- }
1748
- else {
1749
- // --- 不显示 ---
1750
- // --- 还得判断是不是 stopPropagation 了,如果 stopPropagation 了,才 true ---
1751
- if (rtn === -1) {
1752
- oe.stopPropagation();
1753
- gestureWheel.done = true;
1754
- }
1755
- else {
1756
- gestureWheel.dir = '';
1757
- }
1758
- return;
1759
- }
1760
- // --- 重置位置 ---
1761
- form.elements.gesture.style.transform = 'scale(0)';
1762
- switch (gestureWheel.dir) {
1763
- case 'top':
1764
- case 'bottom': {
1765
- form.elements.gesture.style.left = (rect.left + ((rect.width - 20) / 2)).toString() + 'px';
1766
- if (gestureWheel.dir === 'top') {
1767
- form.elements.gesture.style.top = (rect.top + 10).toString() + 'px';
1768
- }
1769
- else {
1770
- form.elements.gesture.style.top = (rect.bottom - 10).toString() + 'px';
1771
- }
1772
- break;
1773
- }
1774
- default: {
1775
- form.elements.gesture.style.top = (rect.top + ((rect.height - 20) / 2)).toString() + 'px';
1776
- if (gestureWheel.dir === 'left') {
1777
- form.elements.gesture.style.left = (rect.left + 10).toString() + 'px';
1778
- }
1779
- else {
1780
- form.elements.gesture.style.left = (rect.right - 10).toString() + 'px';
1781
- }
1782
- }
1783
- }
1784
- gestureWheel.firstTimer = true;
1785
- await tool.sleep(30);
1786
- gestureWheel.firstTimer = false;
1787
- form.elements.gesture.classList.add('ani');
1788
- }
1789
- // --- 滚动 ---
1790
- switch (gestureWheel.dir) {
1791
- case 'top':
1792
- case 'bottom': {
1793
- gestureWheel.offset += (gestureWheel.dir === 'top') ? -deltaY : deltaY;
1794
- break;
1795
- }
1796
- default: {
1797
- gestureWheel.offset += (gestureWheel.dir === 'left') ? -deltaX : deltaX;
1798
- }
1799
- }
1800
- if (gestureWheel.offset < 0) {
1801
- // --- 隐藏了,直接 return ---
1802
- gestureWheel.offset = 0;
1803
- form.elements.gesture.style.opacity = '0';
1804
- return;
1805
- }
1806
- // --- 显示 gesture 对象 ---
1807
- form.elements.gesture.style.opacity = '1';
1808
- let offset = gestureWheel.offset / 1.38;
1809
- if (offset > 90) {
1810
- offset = 90;
1811
- form.elements.gesture.classList.add('done');
1812
- }
1813
- else {
1814
- form.elements.gesture.classList.remove('done');
1815
- }
1816
- // --- 处理标签位置(20 像素是 gesture 的宽高) ---
1817
- form.elements.gesture.style.transform = 'scale(' + (offset / 90).toString() + ')';
1818
- switch (gestureWheel.dir) {
1819
- case 'top': {
1820
- form.elements.gesture.style.top = (rect.top + (offset / 1.5)).toString() + 'px';
1821
- break;
1822
- }
1823
- case 'bottom': {
1824
- form.elements.gesture.style.top = (rect.bottom - 20 - (offset / 1.5)).toString() + 'px';
1825
- break;
1826
- }
1827
- case 'left': {
1828
- form.elements.gesture.style.left = (rect.left + (offset / 1.5)).toString() + 'px';
1829
- break;
1830
- }
1831
- default: {
1832
- form.elements.gesture.style.left = (rect.right - 20 - (offset / 1.5)).toString() + 'px';
1833
- }
1834
- }
1835
- // --- 看是否要响应 ---
1836
- clearTimeout(gestureWheel.timer);
1837
- if (offset < 90) {
1838
- gestureWheel.timer = window.setTimeout(() => {
1839
- form.elements.gesture.style.opacity = '0';
1840
- form.elements.gesture.classList.remove('ani');
1841
- }, 250);
1842
- return;
1843
- }
1844
- gestureWheel.done = true;
1845
- handler?.(gestureWheel.dir as any) as any;
1846
- await tool.sleep(500);
1847
- form.elements.gesture.style.opacity = '0';
1848
- form.elements.gesture.classList.remove('ani');
1849
- })().catch((e) => {
1850
- console.log('error', 'dom.bindGesture', e);
1851
- });
1852
- }
1853
- }
1854
-
1855
- let lastLongTime: number = 0;
1856
-
1857
- export function allowEvent(e: MouseEvent | TouchEvent | KeyboardEvent): boolean {
1858
- const now = Date.now();
1859
- if (now - lastLongTime < 5) {
1860
- return false;
1861
- }
1862
- const current = e.currentTarget as HTMLElement;
1863
- if (current.dataset.cgDisabled !== undefined) {
1864
- return false;
1865
- }
1866
- if (findParentByData(current, 'cg-disabled')) {
1867
- return false;
1868
- }
1869
- return true;
1870
- }
1871
-
1872
- /**
1873
- * --- 绑定长按事件 ---
1874
- * @param e 事件原型
1875
- * @param long 回调
1876
- */
1877
- export function bindLong(e: MouseEvent | TouchEvent, long: (e: MouseEvent | TouchEvent) => void | Promise<void>): void {
1878
- if (hasTouchButMouse(e)) {
1879
- return;
1880
- }
1881
- /** --- 上一次的坐标 --- */
1882
- const tx: number = (e instanceof MouseEvent || e.type === 'mousedown') ? (e as MouseEvent).clientX : e.touches[0].clientX;
1883
- const ty: number = (e instanceof MouseEvent || e.type === 'mousedown') ? (e as MouseEvent).clientY : e.touches[0].clientY;
1884
- let ox: number = 0;
1885
- let oy: number = 0;
1886
- /** --- 是否执行了 long --- */
1887
- let isLong: boolean = false;
1888
- let timer: number | undefined = window.setTimeout(() => {
1889
- clearTimeout(timer);
1890
- timer = undefined;
1891
- if (ox <= 1 && oy <= 1) {
1892
- isLong = true;
1893
- const rtn = long(e);
1894
- if (rtn instanceof Promise) {
1895
- rtn.catch((e) => {
1896
- throw e;
1897
- });
1898
- }
1899
- }
1900
- }, 300);
1901
- bindDown(e, {
1902
- move: (e: MouseEvent | TouchEvent) => {
1903
- const x: number = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
1904
- const y: number = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
1905
- ox = Math.abs(x - tx);
1906
- oy = Math.abs(y - ty);
1907
- },
1908
- up: () => {
1909
- if (timer !== undefined) {
1910
- // --- 肯定没执行 long ---
1911
- clearTimeout(timer);
1912
- timer = undefined;
1913
- }
1914
- else if (isLong) {
1915
- lastLongTime = Date.now();
1916
- }
1917
- }
1918
- });
1919
- }
1920
-
1921
- /** --- 要传输的 drag data 数据 --- */
1922
- let bindDragData: any = undefined;
1923
-
1924
- /**
1925
- * --- 重新绑定 drag 数据 ---
1926
- * @param data 要绑定的数据
1927
- */
1928
- export function setDragData(data?: string | number | boolean | Record<string, any>): void {
1929
- bindDragData = data;
1930
- }
1931
-
1932
- /**
1933
- * --- 绑定拖动 ---
1934
- * @param e 鼠标事件
1935
- * @param opt 参数
1936
- */
1937
- export function bindDrag(e: MouseEvent | TouchEvent, opt: {
1938
- 'el': HTMLElement;
1939
- 'data'?: any;
1940
-
1941
- 'start'?: (x: number, y: number) => any;
1942
- 'move'?: (e: MouseEvent | TouchEvent, opt: types.IBindMoveMoveOptions) => void;
1943
- 'end'?: (moveTimes: Array<{ 'time': number; 'ox': number; 'oy': number; }>, e: MouseEvent | TouchEvent) => void;
1944
- }): void {
1945
- bindDragData = opt.data;
1946
- let otop = 0;
1947
- let oleft = 0;
1948
- let nel: HTMLElement | null = null;
1949
- bindMove(e, {
1950
- 'object': opt.el,
1951
- 'start': function(x, y) {
1952
- const rect = opt.el.getBoundingClientRect();
1953
- form.showDrag();
1954
- form.moveDrag({
1955
- 'top': rect.top,
1956
- 'left': rect.left,
1957
- 'width': rect.width,
1958
- 'height': rect.height,
1959
- 'icon': true
1960
- });
1961
- otop = rect.top;
1962
- oleft = rect.left;
1963
- opt.start?.(x, y);
1964
- },
1965
- 'move': function(e, o) {
1966
- const ntop = otop + o.oy;
1967
- const nleft = oleft + o.ox;
1968
- form.moveDrag({
1969
- 'top': ntop,
1970
- 'left': nleft,
1971
- 'icon': false
1972
- });
1973
- otop = ntop;
1974
- oleft = nleft;
1975
- // --- 获取当前 element ---
1976
- const els = document.elementsFromPoint(o.x, o.y) as HTMLElement[];
1977
- for (const el of els) {
1978
- if (el.dataset.cgDrop === undefined) {
1979
- continue;
1980
- }
1981
- if (el === opt.el) {
1982
- continue;
1983
- }
1984
- if (el === nel) {
1985
- // --- 还是当前的 ---
1986
- return;
1987
- }
1988
- if (nel !== null) {
1989
- nel.removeAttribute('data-cg-hover');
1990
- nel.dispatchEvent(new CustomEvent('dragleave', {
1991
- 'detail': {
1992
- 'value': bindDragData
1993
- }
1994
- }));
1995
- }
1996
- el.dataset.cgHover = '';
1997
- nel = el;
1998
- nel.dispatchEvent(new CustomEvent('dragenter', {
1999
- 'detail': {
2000
- 'value': bindDragData
2001
- }
2002
- }));
2003
- return;
2004
- }
2005
- // --- not found ---
2006
- form.moveDrag({
2007
- 'icon': true
2008
- });
2009
- if (nel === null) {
2010
- return;
2011
- }
2012
- nel.removeAttribute('data-cg-hover');
2013
- nel.dispatchEvent(new CustomEvent('dragleave', {
2014
- 'detail': {
2015
- 'value': bindDragData
2016
- }
2017
- }));
2018
- nel = null;
2019
- opt.move?.(e, o);
2020
- },
2021
- 'end': function(moveTimes, e) {
2022
- form.hideDrag();
2023
- if (nel === null) {
2024
- return;
2025
- }
2026
- nel.removeAttribute('data-cg-hover');
2027
- nel.dispatchEvent(new CustomEvent('drop', {
2028
- 'detail': {
2029
- 'value': bindDragData
2030
- }
2031
- }));
2032
- opt.end?.(moveTimes, e);
2033
- bindDragData = undefined;
2034
- }
2035
- });
2036
- }
2037
-
2038
- /** --- 目前是否已绑定了 bindMove --- */
2039
- export const is = clickgo.vue.reactive({
2040
- 'move': false,
2041
- 'shift': false,
2042
- 'ctrl': false,
2043
- 'meta': false,
2044
- 'full': false
2045
- });
2046
-
2047
- window.addEventListener('keydown', function(e: KeyboardEvent) {
2048
- switch (e.key) {
2049
- case 'Shift': {
2050
- is.shift = true;
2051
- break;
2052
- }
2053
- case 'Control': {
2054
- is.ctrl = true;
2055
- break;
2056
- }
2057
- case 'Meta': {
2058
- is.meta = true;
2059
- break;
2060
- }
2061
- }
2062
- core.trigger('keydown', e);
2063
- });
2064
- window.addEventListener('keyup', function(e: KeyboardEvent) {
2065
- switch (e.key) {
2066
- case 'Shift': {
2067
- is.shift = false;
2068
- break;
2069
- }
2070
- case 'Control': {
2071
- is.ctrl = false;
2072
- break;
2073
- }
2074
- case 'Meta': {
2075
- is.meta = false;
2076
- break;
2077
- }
2078
- }
2079
- core.trigger('keyup', e);
2080
- });
2081
-
2082
- /**
2083
- * --- 绑定拖动事件 ---
2084
- * @param e mousedown 或 touchstart 的 event
2085
- * @param opt 回调选项
2086
- */
2087
- export function bindMove(e: MouseEvent | TouchEvent, opt: types.IBindMoveOptions): types.IBindMoveResult {
2088
- if (hasTouchButMouse(e)) {
2089
- return {
2090
- 'left': 0,
2091
- 'top': 0,
2092
- 'right': 0,
2093
- 'bottom': 0
2094
- };
2095
- }
2096
- is.move = true;
2097
- setGlobalCursor(opt.cursor ? opt.cursor : getComputedStyle(e.target as Element).cursor);
2098
- /** --- 上一次的 x 坐标 --- */
2099
- let tx: number = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
2100
- /** --- 上一次的 y 坐标 --- */
2101
- let ty: number = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
2102
-
2103
- /** --- 拖动限定区域左侧 --- */
2104
- let left: number,
2105
- /** --- 拖动限定区域顶部 --- */
2106
- top: number,
2107
- /** --- 拖动限定区域右侧 --- */
2108
- right: number,
2109
- /** --- 拖动限定区域底部 --- */
2110
- bottom: number;
2111
- if (opt.areaObject) {
2112
- if (!(opt.areaObject instanceof Element)) {
2113
- opt.areaObject = opt.areaObject.$el;
2114
- }
2115
- const areaRect = opt.areaObject.getBoundingClientRect();
2116
- const areaStyle = getComputedStyle(opt.areaObject);
2117
- left = areaRect.left + parseFloat(areaStyle.borderLeftWidth) + parseFloat(areaStyle.paddingLeft);
2118
- top = areaRect.top + parseFloat(areaStyle.borderTopWidth) + parseFloat(areaStyle.paddingTop);
2119
- right = areaRect.left + areaRect.width - (parseFloat(areaStyle.borderRightWidth)
2120
- + parseFloat(areaStyle.paddingRight));
2121
- bottom = areaRect.top + areaRect.height - (parseFloat(areaStyle.borderRightWidth)
2122
- + parseFloat(areaStyle.paddingRight));
2123
- }
2124
- else {
2125
- const area = core.getAvailArea();
2126
- left = opt.left ?? area.left;
2127
- top = opt.top ?? area.top;
2128
- right = opt.right ?? area.width;
2129
- bottom = opt.bottom ?? area.height;
2130
- }
2131
- // --- 限定拖动区域额外补偿(拖动对象和实际对象有一定偏差时使用) ---
2132
- if (opt.offsetLeft) {
2133
- left += opt.offsetLeft;
2134
- }
2135
- if (opt.offsetTop) {
2136
- top += opt.offsetTop;
2137
- }
2138
- if (opt.offsetRight) {
2139
- right += opt.offsetRight;
2140
- }
2141
- if (opt.offsetBottom) {
2142
- bottom += opt.offsetBottom;
2143
- }
2144
-
2145
- /** --- 判断是否已经到达了边界 --- */
2146
- let isBorder: boolean = false;
2147
-
2148
- // --- 限定拖动对象,限定后整体对象将无法拖动出边界 ---
2149
- let objectLeft: number, objectTop: number, objectWidth: number, objectHeight: number;
2150
- /** --- 初始坐标距离 object 左侧的距离 --- */
2151
- let offsetLeft: number = 0,
2152
- /** --- 初始坐标距离 object 顶部的距离 --- */
2153
- offsetTop: number = 0,
2154
- /** --- 初始坐标距离 object 右侧的距离 --- */
2155
- offsetRight: number = 0,
2156
- /** --- 初始坐标距离 object 底部的距离 --- */
2157
- offsetBottom = 0;
2158
-
2159
- /** --- 每次拖动时的时间以及偏移 --- */
2160
- const moveTimes: Array<{ 'time': number; 'ox': number; 'oy': number; }> = [];
2161
-
2162
- bindDown(e, {
2163
- start: () => {
2164
- if (opt.start) {
2165
- if (opt.start(tx, ty) === false) {
2166
- setGlobalCursor();
2167
- return false;
2168
- }
2169
- }
2170
- // --- 限定拖动对象,限定后整体对象将无法拖动出边界 ---
2171
- if (opt.object) {
2172
- if (!(opt.object instanceof Element)) {
2173
- opt.object = opt.object.$el;
2174
- }
2175
- const rect = opt.object.getBoundingClientRect();
2176
- objectLeft = rect.left;
2177
- objectTop = rect.top;
2178
- objectWidth = rect.width;
2179
- objectHeight = rect.height;
2180
- }
2181
- else {
2182
- objectLeft = opt.objectLeft ?? 0;
2183
- objectTop = opt.objectTop ?? 0;
2184
- objectWidth = opt.objectWidth ?? 0;
2185
- objectHeight = opt.objectHeight ?? 0;
2186
- }
2187
-
2188
- // --- 限定边界的偏移,如果限定了拖动对象,则需要根据偏移来判断边界,毕竟拖动点不可能每次都刚好是边界 ---
2189
- if (objectWidth > 0) {
2190
- offsetLeft = tx - objectLeft;
2191
- }
2192
- if (objectHeight > 0) {
2193
- offsetTop = ty - objectTop;
2194
- }
2195
- offsetRight = objectWidth - offsetLeft;
2196
- offsetBottom = objectHeight - offsetTop;
2197
- },
2198
- move: (e: MouseEvent | TouchEvent, dir) => {
2199
- /** --- 本次 x 坐标 --- */
2200
- let x: number,
2201
- /** --- 本次 y 坐标 --- */
2202
- y: number;
2203
- x = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
2204
- y = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
2205
- if (x === tx && y === ty) {
2206
- // --- 没有移动,直接返回 ---
2207
- return;
2208
- }
2209
-
2210
- /** --- 当前是否在顶部边界线上 --- */
2211
- let inBorderTop: boolean = false,
2212
- /** --- 当前是否在右侧边界上 --- */
2213
- inBorderRight: boolean = false,
2214
- /** --- 当前是否在底部边界上 --- */
2215
- inBorderBottom: boolean = false,
2216
- /** --- 当前是否在左侧边界上 --- */
2217
- inBorderLeft: boolean = false;
2218
-
2219
- /** --- 当前理论上可拖动 object 应该存在的 x 左侧 --- */
2220
- const nowLeft = x - offsetLeft;
2221
- /** --- 当前理论上可拖动 object 应该存在的 x 右侧 --- */
2222
- const nowRight = x + offsetRight;
2223
- if (nowLeft <= left) {
2224
- // --- 必定是到左侧边界了 ---
2225
- inBorderLeft = true;
2226
- if (nowLeft < left && x < tx) {
2227
- // --- 当前 x 超越了 left 界限,还在向左移动 ---
2228
- if (tx - offsetLeft > left) {
2229
- // --- 如果刚刚还没超过,则设定为界限值 ---
2230
- x = left + offsetLeft;
2231
- }
2232
- else {
2233
- // --- 如果刚刚就已经超过了,则恢复成刚刚 ---
2234
- x = tx;
2235
- }
2236
- }
2237
- }
2238
- else if (offsetRight !== 0) {
2239
- if (nowRight >= right) {
2240
- // --- 必定到右侧边界 ---
2241
- inBorderRight = true;
2242
- if (nowRight > right && x > tx) {
2243
- if (tx + offsetRight < right) {
2244
- x = right - offsetRight;
2245
- }
2246
- else {
2247
- x = tx;
2248
- }
2249
- }
2250
- }
2251
- }
2252
- else if (offsetRight === 0) {
2253
- const r1 = right - 1;
2254
- if (x >= r1) {
2255
- inBorderRight = true;
2256
- if (x > r1 && x > tx) {
2257
- if (tx < r1) {
2258
- x = r1;
2259
- }
2260
- else {
2261
- x = tx;
2262
- }
2263
- }
2264
- }
2265
- }
2266
-
2267
- /** --- 当前理论上可拖动 object 应该存在的 y 顶部 --- */
2268
- const nowTop = y - offsetTop;
2269
- /** --- 当前理论上可拖动 object 应该存在的 y 底部 --- */
2270
- const nowBottom = y + offsetBottom;
2271
- if (nowTop <= top) {
2272
- inBorderTop = true;
2273
- if (nowTop < top && y < ty) {
2274
- if (ty - offsetTop > top) {
2275
- y = top + offsetTop;
2276
- }
2277
- else {
2278
- y = ty;
2279
- }
2280
- }
2281
- }
2282
- else if (offsetBottom !== 0) {
2283
- if (nowBottom >= bottom) {
2284
- inBorderBottom = true;
2285
- if (nowBottom > bottom && y > ty) {
2286
- if (ty + offsetBottom < bottom) {
2287
- y = bottom - offsetBottom;
2288
- }
2289
- else {
2290
- y = ty;
2291
- }
2292
- }
2293
- }
2294
- }
2295
- else if (offsetBottom === 0) {
2296
- const b1 = bottom - 1;
2297
- if (y >= b1) {
2298
- inBorderBottom = true;
2299
- if (y > b1 && y > ty) {
2300
- if (ty < b1) {
2301
- y = b1;
2302
- }
2303
- else {
2304
- y = ty;
2305
- }
2306
- }
2307
- }
2308
- }
2309
-
2310
- // --- 检测是否执行 borderIn 事件(是否正在边界上) ---
2311
- let border: types.TDomBorder = '';
2312
- if (inBorderTop || inBorderRight || inBorderBottom || inBorderLeft) {
2313
- if (inBorderTop) {
2314
- if (x - left <= 20) {
2315
- border = 'lt';
2316
- }
2317
- else if (right - x <= 20) {
2318
- border = 'tr';
2319
- }
2320
- else {
2321
- border = 't';
2322
- }
2323
- }
2324
- else if (inBorderRight) {
2325
- if (y - top <= 20) {
2326
- border = 'tr';
2327
- }
2328
- else if (bottom - y <= 20) {
2329
- border = 'rb';
2330
- }
2331
- else {
2332
- border = 'r';
2333
- }
2334
- }
2335
- else if (inBorderBottom) {
2336
- if (right - x <= 20) {
2337
- border = 'rb';
2338
- }
2339
- else if (x - left <= 20) {
2340
- border = 'bl';
2341
- }
2342
- else {
2343
- border = 'b';
2344
- }
2345
- }
2346
- else if (inBorderLeft) {
2347
- if (y - top <= 20) {
2348
- border = 'lt';
2349
- }
2350
- else if (bottom - y <= 20) {
2351
- border = 'bl';
2352
- }
2353
- else {
2354
- border = 'l';
2355
- }
2356
- }
2357
-
2358
- if (!isBorder) {
2359
- isBorder = true;
2360
- opt.borderIn?.(x, y, border, e);
2361
- }
2362
- }
2363
- else {
2364
- // --- 不在边界 ---
2365
- if (isBorder) {
2366
- isBorder = false;
2367
- opt.borderOut?.();
2368
- }
2369
- }
2370
-
2371
- const ox = x - tx;
2372
- const oy = y - ty;
2373
- moveTimes.push({
2374
- 'time': Date.now(),
2375
- 'ox': ox,
2376
- 'oy': oy
2377
- });
2378
- opt.move?.(e, {
2379
- 'ox': ox,
2380
- 'oy': oy,
2381
- 'x': x,
2382
- 'y': y,
2383
- 'border': border,
2384
- 'inBorder': {
2385
- 'top': inBorderTop,
2386
- 'right': inBorderRight,
2387
- 'bottom': inBorderBottom,
2388
- 'left': inBorderLeft
2389
- },
2390
- 'dir': dir
2391
- });
2392
- tx = x;
2393
- ty = y;
2394
- },
2395
- up: (e) => {
2396
- is.move = false;
2397
- setGlobalCursor();
2398
- opt.up?.(moveTimes, e);
2399
- },
2400
- end: (e) => {
2401
- opt.end?.(moveTimes, e);
2402
- }
2403
- });
2404
-
2405
- if (opt.showRect) {
2406
- form.showRectangle(tx, ty, {
2407
- 'left': left,
2408
- 'top': top,
2409
- 'width': right - left,
2410
- 'height': bottom - top
2411
- });
2412
- setTimeout(() => {
2413
- form.hideRectangle();
2414
- }, 3000);
2415
- }
2416
-
2417
- return {
2418
- 'left': left,
2419
- 'top': top,
2420
- 'right': right,
2421
- 'bottom': bottom
2422
- };
2423
- }
2424
-
2425
- /**
2426
- * --- 绑定拖动改变大小事件 ---
2427
- * @param e mousedown 或 touchstart 的 event
2428
- * @param opt 选项,width, height 当前对象宽高
2429
- */
2430
- export function bindResize(e: MouseEvent | TouchEvent, opt: types.IBindResizeOptions): void {
2431
- if (hasTouchButMouse(e)) {
2432
- return;
2433
- }
2434
- opt.minWidth = opt.minWidth ?? 0;
2435
- opt.minHeight = opt.minHeight ?? 0;
2436
- /** --- 当前鼠标位置 x --- */
2437
- const x: number = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
2438
- /** --- 当前鼠标位置 y --- */
2439
- const y: number = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
2440
- // --- 获取偏差补偿 ---
2441
- let offsetLeft!: number, offsetTop!: number, offsetRight!: number, offsetBottom!: number;
2442
- /** --- 上下左右界限 --- */
2443
- let left!: number, top!: number, right!: number, bottom!: number;
2444
-
2445
- // --- 获取 object 的 x,y 和 w,h 信息 ---
2446
- if (opt.objectLeft === undefined
2447
- || opt.objectTop === undefined
2448
- || opt.objectWidth === undefined
2449
- || opt.objectHeight === undefined
2450
- ) {
2451
- if (!opt.object) {
2452
- return;
2453
- }
2454
- if (!(opt.object instanceof Element)) {
2455
- opt.object = opt.object.$el;
2456
- }
2457
- const objectRect = opt.object.getBoundingClientRect();
2458
- opt.objectLeft = objectRect.left;
2459
- opt.objectTop = objectRect.top;
2460
- opt.objectWidth = objectRect.width;
2461
- opt.objectHeight = objectRect.height;
2462
- }
2463
-
2464
- if (opt.border === 'tr' || opt.border === 'r' || opt.border === 'rb') {
2465
- // --- ↗、→、↘ ---
2466
- left = opt.objectLeft + opt.minWidth;
2467
- offsetLeft = x - (opt.objectLeft + opt.objectWidth);
2468
- offsetRight = offsetLeft;
2469
- if (opt.maxWidth) {
2470
- right = opt.objectLeft + opt.maxWidth;
2471
- }
2472
- }
2473
- else if (opt.border === 'bl' || opt.border === 'l' || opt.border === 'lt') {
2474
- // --- ↙、←、↖ ---
2475
- right = opt.objectLeft + opt.objectWidth - opt.minWidth;
2476
- offsetLeft = x - opt.objectLeft;
2477
- offsetRight = offsetLeft;
2478
- if (opt.maxWidth) {
2479
- left = opt.objectLeft + opt.objectWidth - opt.maxWidth;
2480
- }
2481
- }
2482
- if (opt.border === 'rb' || opt.border === 'b' || opt.border === 'bl') {
2483
- // --- ↘、↓、↙ ---
2484
- top = opt.objectTop + opt.minHeight;
2485
- offsetTop = y - (opt.objectTop + opt.objectHeight);
2486
- offsetBottom = offsetTop;
2487
- if (opt.maxHeight) {
2488
- bottom = opt.objectTop + opt.maxHeight;
2489
- }
2490
- }
2491
- else if (opt.border === 'lt' || opt.border === 't' || opt.border === 'tr') {
2492
- bottom = opt.objectTop + opt.objectHeight - opt.minHeight;
2493
- offsetTop = y - opt.objectTop;
2494
- offsetBottom = offsetTop;
2495
- if (opt.maxHeight) {
2496
- top = opt.objectTop + opt.objectHeight - opt.maxHeight;
2497
- }
2498
- }
2499
- bindMove(e, {
2500
- 'left': left,
2501
- 'top': top,
2502
- 'right': right,
2503
- 'bottom': bottom,
2504
- 'offsetLeft': offsetLeft,
2505
- 'offsetTop': offsetTop,
2506
- 'offsetRight': offsetRight,
2507
- 'offsetBottom': offsetBottom,
2508
- 'start': opt.start,
2509
- 'move': function(e, o) {
2510
- if (opt.border === 'tr' || opt.border === 'r' || opt.border === 'rb') {
2511
- opt.objectWidth! += o.ox;
2512
- }
2513
- else if (opt.border === 'bl' || opt.border === 'l' || opt.border === 'lt') {
2514
- opt.objectWidth! -= o.ox;
2515
- opt.objectLeft! += o.ox;
2516
- }
2517
- if (opt.border === 'rb' || opt.border === 'b' || opt.border === 'bl') {
2518
- opt.objectHeight! += o.oy;
2519
- }
2520
- else if (opt.border === 'lt' || opt.border === 't' || opt.border === 'tr') {
2521
- opt.objectHeight! -= o.oy;
2522
- opt.objectTop! += o.oy;
2523
- }
2524
- opt.move?.(opt.objectLeft!, opt.objectTop!, opt.objectWidth!, opt.objectHeight!, x, y, o.border);
2525
- },
2526
- 'end': opt.end
2527
- });
2528
- }
2529
-
2530
- /**
2531
- * --- 通过 data 名查找上层所有标签是否存在 ---
2532
- * @param el 当前标签
2533
- * @param name 要查找的 data 名
2534
- * @param value data 对应的值,留空则代表只要匹配了名就可以
2535
- */
2536
- export function findParentByData(el: HTMLElement, name: string, value?: string): HTMLElement | null {
2537
- let parent = el.parentNode as HTMLElement;
2538
- while (parent) {
2539
- if (!parent.tagName) {
2540
- continue;
2541
- }
2542
- if (parent.tagName.toLowerCase() === 'body') {
2543
- break;
2544
- }
2545
- const v = parent.getAttribute('data-' + name);
2546
- if (v !== null) {
2547
- if (value) {
2548
- if (value === v) {
2549
- return parent;
2550
- }
2551
- // --- value 不匹配 ---
2552
- continue;
2553
- }
2554
- return parent;
2555
- }
2556
- parent = parent.parentNode as HTMLElement;
2557
- }
2558
- return null;
2559
- }
2560
-
2561
- /**
2562
- * --- 通过 class 名查找上层所有标签是否存在 ---
2563
- * @param el 当前标签
2564
- * @param name 要查找的 class 名
2565
- */
2566
- export function findParentByClass(el: HTMLElement, name: string): HTMLElement | null {
2567
- let parent = el.parentNode as HTMLElement;
2568
- while (parent) {
2569
- if (!parent.tagName) {
2570
- continue;
2571
- }
2572
- if (parent.tagName.toLowerCase() === 'body') {
2573
- break;
2574
- }
2575
- if (parent.classList.contains(name)) {
2576
- return parent;
2577
- }
2578
- parent = parent.parentNode as HTMLElement;
2579
- }
2580
- return null;
2581
- }
2582
-
2583
- /**
2584
- * --- 通过 tagname 查找上层所有标签是否存在 ---
2585
- * @param el 当前标签
2586
- * @param name 要查找的 tagname 名,小写,如 table
2587
- */
2588
- export function findParentByTag(el: HTMLElement, name: string): HTMLElement | null {
2589
- let parent = el.parentNode as HTMLElement;
2590
- while (parent) {
2591
- if (!parent.tagName) {
2592
- continue;
2593
- }
2594
- const tag = parent.tagName.toLowerCase();
2595
- if (tag === 'body') {
2596
- break;
2597
- }
2598
- if (tag === name) {
2599
- return parent;
2600
- }
2601
- parent = parent.parentNode as HTMLElement;
2602
- }
2603
- return null;
2604
- }
2605
-
2606
- /**
2607
- * --- 判断一个元素是当前同级的第几位 ---
2608
- * @param el 要判断的元素
2609
- */
2610
- export function index(el: HTMLElement): number {
2611
- let index = 0;
2612
- let p = el.previousElementSibling;
2613
- while (true) {
2614
- if (!p) {
2615
- break;
2616
- }
2617
- ++index;
2618
- p = p.previousElementSibling;
2619
- }
2620
- return index;
2621
- }
2622
-
2623
- /**
2624
- * --- 查找指定 el 的同级所有元素 ---
2625
- * @param el 基准
2626
- * @returns HTMLElement[]
2627
- */
2628
- export function siblings(el: HTMLElement): HTMLElement[] {
2629
- if (!el.parentNode) {
2630
- return [];
2631
- }
2632
- const list: HTMLElement[] = [];
2633
- for (let i = 0; i < el.parentNode.children.length; ++i) {
2634
- const e = el.parentNode.children.item(i) as HTMLElement;
2635
- if (e === el) {
2636
- continue;
2637
- }
2638
- list.push(e);
2639
- }
2640
- return list;
2641
- }
2642
-
2643
- /**
2644
- * --- 查找指定 el 的同级的存在 data 的元素 ---
2645
- * @param el 基准
2646
- * @param name data 名,不含 data-
2647
- * @returns HTMLElement[]
2648
- */
2649
- export function siblingsData(el: HTMLElement, name: string): HTMLElement[] {
2650
- const list = siblings(el);
2651
- const olist: HTMLElement[] = [];
2652
- for (const item of list) {
2653
- if (item.getAttribute('data-' + name) === null) {
2654
- continue;
2655
- }
2656
- olist.push(item);
2657
- }
2658
- return olist;
2659
- }
2660
-
2661
- /**
2662
- * --- 全屏 ---
2663
- */
2664
- export async function fullscreen(): Promise<boolean> {
2665
- const he = document.getElementsByTagName('html')[0] as any;
2666
- if (he.webkitRequestFullscreen) {
2667
- await he.webkitRequestFullscreen();
2668
- return true;
2669
- }
2670
- else if (he.requestFullscreen) {
2671
- await he.requestFullscreen();
2672
- return true;
2673
- }
2674
- else {
2675
- return false;
2676
- }
2677
- }
2678
-
2679
- /**
2680
- * --- 退出全屏 ---
2681
- */
2682
- export async function exitFullscreen(): Promise<boolean> {
2683
- const d = document as any;
2684
- if (d.webkitExitFullscreen) {
2685
- await d.webkitExitFullscreen();
2686
- return true;
2687
- }
2688
- else if (d.exitFullscreen) {
2689
- await d.exitFullscreen();
2690
- return true;
2691
- }
2692
- else {
2693
- return false;
2694
- }
2695
- }
2696
-
2697
- /**
2698
- * --- 创建 element ---
2699
- * @param tagName 标签名
2700
- */
2701
- export function createElement<T extends keyof HTMLElementTagNameMap>(tagName: T): HTMLElementTagNameMap[T] {
2702
- return document.createElement(tagName);
2703
- }
2704
-
2705
- // --- 处理 timer 类,窗体消失时不进行监听 ---
2706
- document.addEventListener('visibilitychange', function() {
2707
- if (document.hidden) {
2708
- // --- 隐藏 ---
2709
- cancelAnimationFrame(watchTimer);
2710
- }
2711
- else {
2712
- // --- 显示 ---
2713
- watchTimer = requestAnimationFrame(watchTimerHandler);
2714
- }
2715
- });
2716
-
2717
- // --- 监听 fullscreen 情况的变动 ---
2718
- document.addEventListener('fullscreenchange', function() {
2719
- if ((document as any).webkitFullscreenElement) {
2720
- is.full = true;
2721
- return;
2722
- }
2723
- if (document.fullscreenElement) {
2724
- is.full = true;
2725
- return;
2726
- }
2727
- is.full = false;
2728
- });