clickgo 3.0.0-dev

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 (145) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +75 -0
  3. package/dist/app/demo/config.json +106 -0
  4. package/dist/app/demo/form/control/block/block.css +1 -0
  5. package/dist/app/demo/form/control/block/block.scss +17 -0
  6. package/dist/app/demo/form/control/block/block.xml +7 -0
  7. package/dist/app/demo/form/control/button/button.css +1 -0
  8. package/dist/app/demo/form/control/button/button.js +27 -0
  9. package/dist/app/demo/form/control/button/button.scss +18 -0
  10. package/dist/app/demo/form/control/button/button.xml +126 -0
  11. package/dist/app/demo/form/control/check/check.js +12 -0
  12. package/dist/app/demo/form/control/check/check.xml +13 -0
  13. package/dist/app/demo/form/control/dialog/dialog.js +8 -0
  14. package/dist/app/demo/form/control/dialog/dialog.xml +26 -0
  15. package/dist/app/demo/form/control/file/file.js +23 -0
  16. package/dist/app/demo/form/control/file/file.xml +25 -0
  17. package/dist/app/demo/form/control/form/form.css +1 -0
  18. package/dist/app/demo/form/control/form/form.js +38 -0
  19. package/dist/app/demo/form/control/form/form.scss +9 -0
  20. package/dist/app/demo/form/control/form/form.xml +28 -0
  21. package/dist/app/demo/form/control/greatview/greatview.css +1 -0
  22. package/dist/app/demo/form/control/greatview/greatview.js +92 -0
  23. package/dist/app/demo/form/control/greatview/greatview.scss +22 -0
  24. package/dist/app/demo/form/control/greatview/greatview.xml +89 -0
  25. package/dist/app/demo/form/control/img/img.xml +16 -0
  26. package/dist/app/demo/form/control/label/label.xml +11 -0
  27. package/dist/app/demo/form/control/list/list.css +1 -0
  28. package/dist/app/demo/form/control/list/list.js +194 -0
  29. package/dist/app/demo/form/control/list/list.scss +7 -0
  30. package/dist/app/demo/form/control/list/list.xml +91 -0
  31. package/dist/app/demo/form/control/loading/loading.xml +8 -0
  32. package/dist/app/demo/form/control/marquee/marquee.js +30 -0
  33. package/dist/app/demo/form/control/marquee/marquee.xml +36 -0
  34. package/dist/app/demo/form/control/menu/menu.js +8 -0
  35. package/dist/app/demo/form/control/menu/menu.xml +122 -0
  36. package/dist/app/demo/form/control/monaco/monaco.js +113 -0
  37. package/dist/app/demo/form/control/monaco/monaco.xml +27 -0
  38. package/dist/app/demo/form/control/overflow/overflow.css +1 -0
  39. package/dist/app/demo/form/control/overflow/overflow.js +70 -0
  40. package/dist/app/demo/form/control/overflow/overflow.scss +18 -0
  41. package/dist/app/demo/form/control/overflow/overflow.xml +98 -0
  42. package/dist/app/demo/form/control/property/property.js +129 -0
  43. package/dist/app/demo/form/control/property/property.xml +6 -0
  44. package/dist/app/demo/form/control/radio/radio.js +7 -0
  45. package/dist/app/demo/form/control/radio/radio.xml +12 -0
  46. package/dist/app/demo/form/control/scroll/scroll.js +14 -0
  47. package/dist/app/demo/form/control/scroll/scroll.xml +35 -0
  48. package/dist/app/demo/form/control/select/select.js +91 -0
  49. package/dist/app/demo/form/control/select/select.xml +74 -0
  50. package/dist/app/demo/form/control/tab/tab.js +75 -0
  51. package/dist/app/demo/form/control/tab/tab.xml +22 -0
  52. package/dist/app/demo/form/control/text/text.js +53 -0
  53. package/dist/app/demo/form/control/text/text.xml +37 -0
  54. package/dist/app/demo/form/control/view/view.css +1 -0
  55. package/dist/app/demo/form/control/view/view.js +73 -0
  56. package/dist/app/demo/form/control/view/view.scss +18 -0
  57. package/dist/app/demo/form/control/view/view.xml +94 -0
  58. package/dist/app/demo/form/event/form/form.css +1 -0
  59. package/dist/app/demo/form/event/form/form.js +129 -0
  60. package/dist/app/demo/form/event/form/form.scss +24 -0
  61. package/dist/app/demo/form/event/form/form.xml +16 -0
  62. package/dist/app/demo/form/event/screen/screen.js +51 -0
  63. package/dist/app/demo/form/event/screen/screen.xml +9 -0
  64. package/dist/app/demo/form/event/task/task.js +78 -0
  65. package/dist/app/demo/form/event/task/task.xml +20 -0
  66. package/dist/app/demo/form/main.css +1 -0
  67. package/dist/app/demo/form/main.js +25 -0
  68. package/dist/app/demo/form/main.scss +9 -0
  69. package/dist/app/demo/form/main.xml +49 -0
  70. package/dist/app/demo/form/method/core/core.js +25 -0
  71. package/dist/app/demo/form/method/core/core.xml +7 -0
  72. package/dist/app/demo/form/method/dom/dom.css +1 -0
  73. package/dist/app/demo/form/method/dom/dom.js +163 -0
  74. package/dist/app/demo/form/method/dom/dom.scss +10 -0
  75. package/dist/app/demo/form/method/dom/dom.xml +55 -0
  76. package/dist/app/demo/form/method/form/form.css +1 -0
  77. package/dist/app/demo/form/method/form/form.js +217 -0
  78. package/dist/app/demo/form/method/form/form.scss +3 -0
  79. package/dist/app/demo/form/method/form/form.xml +56 -0
  80. package/dist/app/demo/form/method/form/test.xml +5 -0
  81. package/dist/app/demo/form/method/fs/fs.js +88 -0
  82. package/dist/app/demo/form/method/fs/fs.xml +8 -0
  83. package/dist/app/demo/form/method/fs/text.js +15 -0
  84. package/dist/app/demo/form/method/fs/text.xml +3 -0
  85. package/dist/app/demo/form/method/task/locale1.json +3 -0
  86. package/dist/app/demo/form/method/task/locale2.json +3 -0
  87. package/dist/app/demo/form/method/task/task.js +153 -0
  88. package/dist/app/demo/form/method/task/task.xml +57 -0
  89. package/dist/app/demo/form/method/theme/theme.js +74 -0
  90. package/dist/app/demo/form/method/theme/theme.xml +9 -0
  91. package/dist/app/demo/form/method/tool/tool.js +64 -0
  92. package/dist/app/demo/form/method/tool/tool.xml +26 -0
  93. package/dist/app/demo/form/method/zip/zip.js +99 -0
  94. package/dist/app/demo/form/method/zip/zip.xml +12 -0
  95. package/dist/app/demo/global.css +3 -0
  96. package/dist/app/demo/res/icon.svg +1 -0
  97. package/dist/app/demo/res/img.jpg +0 -0
  98. package/dist/app/demo/res/r-1.svg +1 -0
  99. package/dist/app/demo/res/r-2.svg +1 -0
  100. package/dist/app/demo/res/sql.svg +1 -0
  101. package/dist/app/demo/res/txt.svg +1 -0
  102. package/dist/app/demo/res/zip.svg +1 -0
  103. package/dist/app/task/config.json +29 -0
  104. package/dist/app/task/form/bar/bar.js +299 -0
  105. package/dist/app/task/form/bar/bar.xml +47 -0
  106. package/dist/app/task/form/desktop/desktop.xml +1 -0
  107. package/dist/app/task/locale/en.json +11 -0
  108. package/dist/app/task/locale/ja.json +11 -0
  109. package/dist/app/task/locale/sc.json +11 -0
  110. package/dist/app/task/locale/tc.json +11 -0
  111. package/dist/clickgo.js +41 -0
  112. package/dist/clickgo.ts +51 -0
  113. package/dist/control/common.cgc +0 -0
  114. package/dist/control/form.cgc +0 -0
  115. package/dist/control/monaco.cgc +0 -0
  116. package/dist/control/property.cgc +0 -0
  117. package/dist/control/task.cgc +0 -0
  118. package/dist/global.css +1 -0
  119. package/dist/icon.png +0 -0
  120. package/dist/index.js +88 -0
  121. package/dist/index.ts +92 -0
  122. package/dist/lib/control.js +365 -0
  123. package/dist/lib/control.ts +428 -0
  124. package/dist/lib/core.js +668 -0
  125. package/dist/lib/core.ts +732 -0
  126. package/dist/lib/dom.js +1471 -0
  127. package/dist/lib/dom.ts +1785 -0
  128. package/dist/lib/form.js +2101 -0
  129. package/dist/lib/form.ts +2496 -0
  130. package/dist/lib/fs.js +849 -0
  131. package/dist/lib/fs.ts +995 -0
  132. package/dist/lib/native.js +138 -0
  133. package/dist/lib/native.ts +219 -0
  134. package/dist/lib/task.js +686 -0
  135. package/dist/lib/task.ts +842 -0
  136. package/dist/lib/theme.js +159 -0
  137. package/dist/lib/theme.ts +196 -0
  138. package/dist/lib/tool.js +501 -0
  139. package/dist/lib/tool.ts +620 -0
  140. package/dist/lib/zip.js +352 -0
  141. package/dist/lib/zip.ts +434 -0
  142. package/dist/theme/familiar.cgt +0 -0
  143. package/package.json +27 -0
  144. package/types/dev.d.ts +30 -0
  145. package/types/index.d.ts +673 -0
@@ -0,0 +1,1785 @@
1
+ /**
2
+ * Copyright 2022 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 form from './form';
18
+ import * as core from './core';
19
+
20
+ /** --- style list 的 div --- */
21
+ const topClass: string[] = ['#cg-form-list', '#cg-pop-list', '#cg-system', '#cg-simpletask'];
22
+ function classUnfold(after?: string): string {
23
+ const arr: string[] = [];
24
+ for (const name of topClass) {
25
+ arr.push(name + (after ? (' ' + after) : ''));
26
+ }
27
+ return arr.join(', ');
28
+ }
29
+
30
+ /*
31
+ #cg-gesture {box-sizing: border-box; position: fixed; z-index: 20020004; border: solid 3px #ff976a; border-radius: 50%; filter: drop-shadow(0 0 3px #ff976a); pointer-events: none; opacity: 0; transform: scale(0); width: 20px; height: 20px;}
32
+ #cg-gesture.done {background: #ff976a;}
33
+ */
34
+ const styleList: HTMLDivElement = document.createElement('div');
35
+ styleList.style.display = 'none';
36
+ document.getElementsByTagName('body')[0].appendChild(styleList);
37
+ styleList.insertAdjacentHTML('beforeend', '<style id=\'cg-global-cursor\'></style>');
38
+ styleList.insertAdjacentHTML('beforeend', `<style id='cg-global'>
39
+ ${classUnfold()} {-webkit-user-select: none; user-select: none; position: fixed; left: 0; top: 0; width: 0; height: 0; cursor: default; box-sizing: border-box;}
40
+ ${classUnfold('img')} {vertical-align: bottom;}
41
+ ${classUnfold('::selection')} {background-color: rgba(0, 0, 0, .1);}
42
+ ${classUnfold('*')}, ${classUnfold('*::after')}, ${classUnfold('*::before')} {box-sizing: border-box; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); flex-shrink: 0;}
43
+ ${classUnfold()}, ${classUnfold('input')}, ${classUnfold('textarea')} {font-family: "Lucida Sans Unicode", "Helvetica Neue","Helvetica","PingFang SC","Hiragino Sans GB","Noto Sans CJK SC","Noto Sans CJK","Source Han Sans","WenQuanYi Micro Hei","Microsoft YaHei",sans-serif; font-size: 12px; line-height: 1; -webkit-font-smoothing: antialiased;}
44
+ </style>`);
45
+
46
+ /** --- 全局 cursor 设置的 style 标签 --- */
47
+ let globalCursorStyle: HTMLStyleElement;
48
+ /**
49
+ * --- 设置全局鼠标样式 ---
50
+ * @param type 样式或留空,留空代表取消
51
+ */
52
+ export function setGlobalCursor(type?: string): void {
53
+ if (!globalCursorStyle) {
54
+ globalCursorStyle = document.getElementById('cg-global-cursor') as HTMLStyleElement;
55
+ }
56
+ if (type) {
57
+ globalCursorStyle.innerHTML = `* {cursor: ${type} !important;}`;
58
+ }
59
+ else {
60
+ globalCursorStyle.innerHTML = '';
61
+ }
62
+ }
63
+
64
+ /** --- 最后一次 touchstart 的时间戳 */
65
+ let lastTouchTime: number = 0;
66
+ // --- 添加 touchstart 事件,既优化了点击行为,也记录了 touch 的时间戳信息 ---
67
+ document.addEventListener('touchstart', function() {
68
+ lastTouchTime = Date.now();
69
+ return;
70
+ }, {
71
+ 'passive': true
72
+ });
73
+
74
+ /**
75
+ * --- 判断当前的事件是否是含有 touch 的设备触发的,如果当前就是 touch 则直接返回 false(false 代表 OK,true 代表 touch 设备却触发了 mouse 事件) ---
76
+ */
77
+ export function hasTouchButMouse(e: MouseEvent | TouchEvent | PointerEvent): boolean {
78
+ if (e instanceof TouchEvent) {
79
+ return false;
80
+ }
81
+ if (((e as any).pointerType === 'touch') && (e.type === 'contextmenu')) {
82
+ // --- 当前是 mouse 但是却是 touch 触发的 ---
83
+ return true;
84
+ }
85
+ const now = Date.now();
86
+ if (now - lastTouchTime < 1000 * 60) {
87
+ // --- 当前是 mouse 但是 10000ms 内有 touch start ---
88
+ return true;
89
+ }
90
+ return false;
91
+ }
92
+
93
+ /**
94
+ * --- 创建任务时连同一起创建的 style 标签 ---
95
+ * @param taskId 任务 id
96
+ */
97
+ export function createToStyleList(taskId: number): void {
98
+ styleList.insertAdjacentHTML('beforeend', `<div id="cg-style-task${taskId}"><style class="cg-style-global"></style><div class="cg-style-control"></div><div class="cg-style-theme"></div><div class="cg-style-form"></div></div>`);
99
+ }
100
+
101
+ /**
102
+ * --- 任务结束时需要移除 task 的所有 style ---
103
+ * @param taskId 任务 id
104
+ */
105
+ export function removeFromStyleList(taskId: number): void {
106
+ document.getElementById('cg-style-task' + taskId.toString())?.remove();
107
+ }
108
+
109
+ /**
110
+ * --- 将 style 内容写入 dom ---
111
+ * @param taskId 当前任务 ID
112
+ * @param style 样式内容
113
+ * @param type 插入的类型
114
+ * @param formId 当前窗体 ID(global 下可空,theme 下为主题唯一标识符)
115
+ */
116
+ export function pushStyle(taskId: number, style: string, type: 'global' | 'theme' | 'control' | 'form' = 'global', formId: number | string = 0): void {
117
+ const el = document.querySelector(`#cg-style-task${taskId} > .cg-style-${type}`);
118
+ if (!el) {
119
+ return;
120
+ }
121
+ if (type === 'global') {
122
+ el.innerHTML = style;
123
+ }
124
+ else if (type === 'theme' || type === 'control') {
125
+ el.insertAdjacentHTML('beforeend', `<style data-name="${formId}">${style}</style>`);
126
+ }
127
+ else {
128
+ el.insertAdjacentHTML('beforeend', `<style class="cg-style-form${formId}">${style}</style>`);
129
+ }
130
+ }
131
+
132
+ /**
133
+ * --- 移除 style 样式 dom ---
134
+ * @param taskId 要移除的任务 ID
135
+ * @param type 移除的类型
136
+ * @param formId 要移除的窗体 ID
137
+ */
138
+ export function removeStyle(taskId: number, type: 'global' | 'theme' | 'control' | 'form' = 'global', formId: number | string = 0): void {
139
+ const styleTask = document.getElementById('cg-style-task' + taskId.toString());
140
+ if (!styleTask) {
141
+ return;
142
+ }
143
+ if (type === 'global') {
144
+ const el = styleTask.querySelector(`.cg-style-global`);
145
+ if (!el) {
146
+ return;
147
+ }
148
+ el.innerHTML = '';
149
+ }
150
+ else if (type === 'theme' || type === 'control') {
151
+ if (formId === 0) {
152
+ const el = styleTask.querySelector(`.cg-style-${type}`);
153
+ if (!el) {
154
+ return;
155
+ }
156
+ el.innerHTML = '';
157
+ }
158
+ else {
159
+ const elist = styleTask.querySelectorAll(`.cg-style-${type} > [data-name='${formId}']`);
160
+ for (let i = 0; i < elist.length; ++i) {
161
+ elist.item(i).remove();
162
+ }
163
+ }
164
+ }
165
+ else {
166
+ const elist = styleTask.querySelectorAll('.cg-style-form' + formId.toString());
167
+ for (let i = 0; i < elist.length; ++i) {
168
+ elist.item(i).remove();
169
+ }
170
+ }
171
+ }
172
+
173
+ /**
174
+ * --- 获取当前任务中子类有几个子元素 ---
175
+ * @param taskId 任务 ID
176
+ * @param type 类型
177
+ */
178
+ export function getStyleCount(taskId: number, type: 'theme' | 'control' | 'form'): number {
179
+ return document.querySelectorAll(`#cg-style-task${taskId} > .cg-style-${type} > style`).length;
180
+ }
181
+
182
+ /**
183
+ * --- 获取实时的 DOM SIZE ---
184
+ * @param el 要获取的 dom
185
+ */
186
+ export function getSize(el: HTMLElement): types.IDomSize {
187
+ const rect = el.getBoundingClientRect();
188
+ const cs = getComputedStyle(el);
189
+ const border = {
190
+ 'top': parseFloat(cs.borderTopWidth),
191
+ 'right': parseFloat(cs.borderRightWidth),
192
+ 'bottom': parseFloat(cs.borderBottomWidth),
193
+ 'left': parseFloat(cs.borderLeftWidth)
194
+ };
195
+ const padding = {
196
+ 'top': parseFloat(cs.paddingTop),
197
+ 'right': parseFloat(cs.paddingRight),
198
+ 'bottom': parseFloat(cs.paddingBottom),
199
+ 'left': parseFloat(cs.paddingLeft)
200
+ };
201
+ return {
202
+ 'top': rect.top,
203
+ 'right': rect.right,
204
+ 'bottom': rect.bottom,
205
+ 'left': rect.left,
206
+ 'width': rect.width,
207
+ 'height': rect.height,
208
+ 'padding': padding,
209
+ 'border': border,
210
+ 'clientWidth': rect.width - border.left - border.right,
211
+ 'clientHeight': rect.height - border.top - border.bottom,
212
+ 'innerWidth': rect.width - border.left - border.right - padding.left - padding.right,
213
+ 'innerHeight': rect.height - border.top - border.bottom - padding.top - padding.bottom,
214
+ 'scrollWidth': el.scrollWidth,
215
+ 'scrollHeight': el.scrollHeight
216
+ };
217
+ }
218
+
219
+ /** --- 被监视中的元素 --- */
220
+ const watchSizeList: types.IWatchSizeItem[] = [];
221
+
222
+ /**
223
+ * --- 添加监视 Element 对象大小,元素移除后自动停止监视(浏览器原生效果) ---
224
+ * @param el 要监视的大小
225
+ * @param cb 回调函数
226
+ * @param immediate 立刻先执行一次回调
227
+ * @param taskId 归属到一个任务里可留空,App 模式下无效
228
+ */
229
+ export function watchSize(
230
+ el: HTMLElement,
231
+ cb: (size: types.IDomSize) => Promise<void> | void,
232
+ immediate: boolean = false,
233
+ taskId?: number
234
+ ): types.IDomSize {
235
+ const fsize = getSize(el);
236
+ for (const item of watchSizeList) {
237
+ if (item.el === el) {
238
+ return fsize;
239
+ }
240
+ }
241
+ if (immediate) {
242
+ const r = cb(fsize);
243
+ if (r instanceof Promise) {
244
+ r.catch(function(e) {
245
+ console.log(e);
246
+ });
247
+ }
248
+ }
249
+ const resizeObserver = new (window as any).ResizeObserver(function(): void {
250
+ const size = getSize(el);
251
+ if (Number.isNaN(size.clientWidth)) {
252
+ return;
253
+ }
254
+ const r = cb(size);
255
+ if (r instanceof Promise) {
256
+ r.catch(function(e) {
257
+ console.log(e);
258
+ });
259
+ }
260
+ });
261
+ resizeObserver.observe(el);
262
+ watchSizeList.push({
263
+ 'el': el,
264
+ 'ro': resizeObserver,
265
+ 'taskId': taskId
266
+ });
267
+ return fsize;
268
+ }
269
+
270
+ /**
271
+ * --- 移除监视 Element 对象大小
272
+ * @param el 要移除监视
273
+ * @param taskId 校验任务 id,App 模式下无效
274
+ */
275
+ export function unwatchSize(el: HTMLElement, taskId?: number): void {
276
+ for (let i = 0; i < watchSizeList.length; ++i) {
277
+ const item = watchSizeList[i];
278
+ if (item.el !== el) {
279
+ continue;
280
+ }
281
+ if (taskId && taskId !== item.taskId) {
282
+ // --- taskId 校验失败 ---
283
+ return;
284
+ }
285
+ // --- 要移除 ---
286
+ if (item.el.offsetParent) {
287
+ item.ro.unobserve(item.el);
288
+ }
289
+ watchSizeList.splice(i, 1);
290
+ --i;
291
+ return;
292
+ }
293
+ }
294
+
295
+ /**
296
+ * --- 清除某个任务下面的所有监视 ---
297
+ * @param taskId 任务 id,App 模式下无效
298
+ */
299
+ export function clearWatchSize(taskId?: number): void {
300
+ if (!taskId) {
301
+ return;
302
+ }
303
+ for (let i = 0; i < watchSizeList.length; ++i) {
304
+ const item = watchSizeList[i];
305
+ if (taskId !== item.taskId) {
306
+ continue;
307
+ }
308
+ if (item.el.offsetParent) {
309
+ item.ro.unobserve(item.el);
310
+ }
311
+ watchSizeList.splice(i, 1);
312
+ --i;
313
+ }
314
+ }
315
+
316
+ /**
317
+ * --- 每隔一段时间清除一次无效的 el ---
318
+ */
319
+ function cgClearWatchSize(): void {
320
+ for (let i = 0; i < watchSizeList.length; ++i) {
321
+ const item = watchSizeList[i];
322
+ if (item.el.offsetParent) {
323
+ continue;
324
+ }
325
+ watchSizeList.splice(i, 1);
326
+ --i;
327
+ }
328
+ }
329
+ setInterval(cgClearWatchSize, 1000 * 60 * 5);
330
+
331
+ /** --- 监视 dom 变动中的元素 */
332
+ const watchList: types.IWatchItem[] = [];
333
+
334
+ /**
335
+ * --- 添加 DOM 内容变化监视 ---
336
+ * @param el dom 对象
337
+ * @param cb 回调
338
+ * @param mode 监听模式
339
+ * @param taskId 归属到一个任务里可留空,App 模式下无效
340
+ */
341
+ export function watch(el: HTMLElement, cb: () => void, mode: 'child' | 'childsub' | 'style' | 'default' = 'default', immediate: boolean = false, taskId?: number): void {
342
+ if (immediate) {
343
+ cb();
344
+ }
345
+ let moi: MutationObserverInit;
346
+ switch (mode) {
347
+ case 'child': {
348
+ moi = {
349
+ 'childList': true
350
+ };
351
+ break;
352
+ }
353
+ case 'childsub': {
354
+ moi = {
355
+ 'childList': true,
356
+ 'subtree': true
357
+ };
358
+ break;
359
+ }
360
+ case 'style': {
361
+ moi = {
362
+ 'attributeFilter': ['style', 'class'],
363
+ 'attributeOldValue': true,
364
+ 'attributes': true
365
+ };
366
+ break;
367
+ }
368
+ case 'default': {
369
+ moi = {
370
+ 'attributeFilter': ['style', 'class'],
371
+ 'attributeOldValue': true,
372
+ 'attributes': true,
373
+ 'characterData': true,
374
+ 'childList': true,
375
+ 'subtree': true
376
+ };
377
+ break;
378
+ }
379
+ default: {
380
+ moi = mode;
381
+ }
382
+ }
383
+ const mo = new MutationObserver(cb);
384
+ mo.observe(el, moi);
385
+ watchList.push({
386
+ 'el': el,
387
+ 'mo': mo,
388
+ 'taskId': taskId
389
+ });
390
+ /*
391
+ {
392
+ 'attributeFilter': ['style', 'class'],
393
+ 'attributes': true,
394
+ 'characterData': true,
395
+ 'childList': true,
396
+ 'subtree': true
397
+ }
398
+ */
399
+ }
400
+
401
+ /**
402
+ * --- 移除监视 Element 对象大小
403
+ * @param el 要移除监视
404
+ * @param taskId 校验任务 id 可留空,App 模式下无效
405
+ */
406
+ export function unwatch(el: HTMLElement, taskId?: number): void {
407
+ for (let i = 0; i < watchList.length; ++i) {
408
+ const item = watchList[i];
409
+ if (item.el !== el) {
410
+ continue;
411
+ }
412
+ if (taskId && taskId !== item.taskId) {
413
+ // --- taskId 校验失败 ---
414
+ return;
415
+ }
416
+ // --- 要移除 ---
417
+ if (item.el.offsetParent) {
418
+ item.mo.disconnect();
419
+ }
420
+ watchList.splice(i, 1);
421
+ --i;
422
+ return;
423
+ }
424
+ }
425
+
426
+ /**
427
+ * --- 清除某个任务下面的所有 watch 监视 ---
428
+ * @param taskId 任务 id,App 模式下无效
429
+ */
430
+ export function clearWatch(taskId?: number): void {
431
+ if (!taskId) {
432
+ return;
433
+ }
434
+ for (let i = 0; i < watchList.length; ++i) {
435
+ const item = watchList[i];
436
+ if (taskId !== item.taskId) {
437
+ continue;
438
+ }
439
+ if (item.el.offsetParent) {
440
+ item.mo.disconnect();
441
+ }
442
+ watchList.splice(i, 1);
443
+ --i;
444
+ }
445
+ }
446
+
447
+ /**
448
+ * --- 每隔一段时间清除一次无效的 watch el ---
449
+ */
450
+ function cgClearWatch(): void {
451
+ for (let i = 0; i < watchList.length; ++i) {
452
+ const item = watchList[i];
453
+ if (item.el.offsetParent) {
454
+ continue;
455
+ }
456
+ watchList.splice(i, 1);
457
+ --i;
458
+ }
459
+ }
460
+ setInterval(cgClearWatch, 1000 * 60 * 5);
461
+
462
+ const watchStyleObjects: Array<{
463
+ 'el': HTMLElement;
464
+ 'sd': CSSStyleDeclaration;
465
+ 'names': Record<string, {
466
+ 'old': string;
467
+ 'cb': Array<(name: string, value: string) => void>;
468
+ }>;
469
+ }> = [];
470
+ export function watchStyle(
471
+ el: HTMLElement,
472
+ name: string | string[],
473
+ cb: (name: string, value: string) => void,
474
+ immediate: boolean = false
475
+ ): void {
476
+ if (typeof name === 'string') {
477
+ name = [name];
478
+ }
479
+ for (const item of watchStyleObjects) {
480
+ if (item.el !== el) {
481
+ continue;
482
+ }
483
+ // --- 已经有监听了 ---
484
+ for (const n of name) {
485
+ if (!item.names[n]) {
486
+ item.names[n] = {
487
+ 'old': (item.sd as any)[n],
488
+ 'cb': [cb]
489
+ };
490
+ }
491
+ else {
492
+ item.names[n].cb.push(cb);
493
+ }
494
+ if (immediate) {
495
+ cb(n, (item.sd as any)[n]);
496
+ }
497
+ }
498
+ return;
499
+ }
500
+ // --- 创建监听 ---
501
+ const sd = getComputedStyle(el);
502
+ watchStyleObjects.push({
503
+ 'el': el,
504
+ 'sd': sd,
505
+ 'names': {}
506
+ });
507
+ const item = watchStyleObjects[watchStyleObjects.length - 1];
508
+ for (const n of name) {
509
+ item.names[n] = {
510
+ 'old': (item.sd as any)[n],
511
+ 'cb': [cb]
512
+ };
513
+ if (immediate) {
514
+ cb(n, (item.sd as any)[n]);
515
+ }
516
+ }
517
+ }
518
+ const watchStyleRAF = function(): void {
519
+ for (let i = 0; i < watchStyleObjects.length; ++i) {
520
+ const item = watchStyleObjects[i];
521
+ if (watchStyleObjects[i].sd.flex === '') {
522
+ watchStyleObjects.splice(i, 1);
523
+ --i;
524
+ continue;
525
+ }
526
+ // --- 执行 cb ---
527
+ for (const name in item.names) {
528
+ if ((item.sd as any)[name] === item.names[name].old) {
529
+ continue;
530
+ }
531
+ item.names[name].old = (item.sd as any)[name];
532
+ for (const cb of item.names[name].cb) {
533
+ cb(name, (item.sd as any)[name]);
534
+ }
535
+ }
536
+ }
537
+ requestAnimationFrame(watchStyleRAF);
538
+ };
539
+ watchStyleRAF();
540
+
541
+ /**
542
+ * --- 检测一个标签是否正在被 watchStyle ---
543
+ * @param el 要检测的标签
544
+ */
545
+ export function isWatchStyle(el: HTMLElement): boolean {
546
+ for (const item of watchStyleObjects) {
547
+ if (item.el !== el) {
548
+ continue;
549
+ }
550
+ return true;
551
+ }
552
+ return false;
553
+ }
554
+
555
+ /**
556
+ * --- 绑定按下以及弹起事件 ---
557
+ * @param e MouseEvent | TouchEvent
558
+ * @param opt 回调选项
559
+ */
560
+ export function bindDown(oe: MouseEvent | TouchEvent, opt: types.IBindDownOptions): void {
561
+ if (hasTouchButMouse(oe)) {
562
+ return;
563
+ }
564
+ /** --- 上一次的坐标 --- */
565
+ let ox: number, oy: number;
566
+ if (oe instanceof MouseEvent) {
567
+ ox = oe.clientX;
568
+ oy = oe.clientY;
569
+ }
570
+ else {
571
+ ox = oe.touches[0].clientX;
572
+ oy = oe.touches[0].clientY;
573
+ }
574
+
575
+ /** --- 是否是第一次执行 move --- */
576
+ let isStart: boolean = false;
577
+
578
+ let end: ((e: MouseEvent | TouchEvent) => void) | undefined = undefined;
579
+ const move = function(e: MouseEvent | TouchEvent): void {
580
+ // --- 虽然上层已经有 preventDefault 了,但是有可能 e.target 会被注销,这样就响应不到上层的 preventDefault 事件,所以要在这里再加一个 ---
581
+ if (!e.target || !(e.target as HTMLElement).offsetParent) {
582
+ e.preventDefault();
583
+ }
584
+ /** --- 本次的移动方向 --- */
585
+ let dir: 'top' | 'right' | 'bottom' | 'left' = 'top';
586
+ const x: number = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
587
+ const y: number = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
588
+ if (x === ox && y === oy) {
589
+ return;
590
+ }
591
+ const xx = x - ox;
592
+ const xy = y - oy;
593
+ if (Math.abs(xy) > Math.abs(xx)) {
594
+ // --- 竖向滚动 ---
595
+ if (xy < 0) {
596
+ // -- 向上移 ---
597
+ dir = 'top';
598
+ }
599
+ else {
600
+ // -- 向下移 ---
601
+ dir = 'bottom';
602
+ }
603
+ }
604
+ else {
605
+ // --- 横向滚动 ---
606
+ if (xx < 0) {
607
+ // -- 向左移 ---
608
+ dir = 'left';
609
+ }
610
+ else {
611
+ // -- 向右移 ---
612
+ dir = 'right';
613
+ }
614
+ }
615
+ ox = x;
616
+ oy = y;
617
+
618
+ if (!isStart) {
619
+ isStart = true;
620
+ if (opt.start && (opt.start(e) === false)) {
621
+ if (e instanceof MouseEvent) {
622
+ window.removeEventListener('mousemove', move);
623
+ window.removeEventListener('mouseup', end!);
624
+ }
625
+ else {
626
+ (oe.target as HTMLElement).removeEventListener('touchmove', move);
627
+ (oe.target as HTMLElement).removeEventListener('touchend', end!);
628
+ (oe.target as HTMLElement).removeEventListener('touchcancel', end!);
629
+ }
630
+ return;
631
+ }
632
+ }
633
+ if (opt.move && (opt.move(e, dir) === false)) {
634
+ if (e instanceof MouseEvent) {
635
+ window.removeEventListener('mousemove', move);
636
+ window.removeEventListener('mouseup', end!);
637
+ }
638
+ else {
639
+ if (oe.target) {
640
+ (oe.target as HTMLElement).removeEventListener('touchmove', move);
641
+ (oe.target as HTMLElement).removeEventListener('touchend', end!);
642
+ (oe.target as HTMLElement).removeEventListener('touchcancel', end!);
643
+ }
644
+ }
645
+ return;
646
+ }
647
+ };
648
+ end = function(e: MouseEvent | TouchEvent): void {
649
+ if (e instanceof MouseEvent) {
650
+ window.removeEventListener('mousemove', move);
651
+ window.removeEventListener('mouseup', end!);
652
+ }
653
+ else {
654
+ if (oe.target) {
655
+ (oe.target as HTMLElement).removeEventListener('touchmove', move);
656
+ (oe.target as HTMLElement).removeEventListener('touchend', end!);
657
+ (oe.target as HTMLElement).removeEventListener('touchcancel', end!);
658
+ }
659
+ }
660
+ opt.up?.(e);
661
+ if (isStart) {
662
+ opt.end?.(e);
663
+ }
664
+ };
665
+ if (oe instanceof MouseEvent) {
666
+ window.addEventListener('mousemove', move, {
667
+ 'passive': false
668
+ });
669
+ window.addEventListener('mouseup', end);
670
+ }
671
+ else {
672
+ (oe.target as HTMLElement).addEventListener('touchmove', move, {
673
+ 'passive': false
674
+ });
675
+ (oe.target as HTMLElement).addEventListener('touchend', end);
676
+ (oe.target as HTMLElement).addEventListener('touchcancel', end);
677
+ }
678
+ opt.down?.(oe);
679
+ }
680
+
681
+ const bindGestureData: {
682
+ 'el': HTMLElement | null;
683
+ 'xx': number;
684
+ 'xy': number;
685
+ 'tx': number;
686
+ 'ty': number;
687
+ 'dir': 'top' | 'right' | 'bottom' | 'left' | null;
688
+ 'timers': {
689
+ 'ani': number;
690
+ 'sleep': number;
691
+ };
692
+ } = {
693
+ 'el': null,
694
+ 'xx': 0, // 当前 x
695
+ 'xy': 0,
696
+ 'tx': 0, // 最终 x
697
+ 'ty': 0,
698
+ 'dir': null,
699
+ 'timers': {
700
+ 'ani': 0,
701
+ 'sleep': 0
702
+ }
703
+ };
704
+
705
+ function clearGestureData(): void {
706
+ bindGestureData.xx = 0;
707
+ bindGestureData.xy = 0;
708
+ bindGestureData.tx = 0;
709
+ bindGestureData.ty = 0;
710
+ }
711
+
712
+ function bindGestureAnimation(opt: { 'rect': DOMRect; 'dirs'?: Array<('top' | 'right' | 'bottom' | 'left')>; handler: (dir: 'top' | 'right' | 'bottom' | 'left') => void; }): void {
713
+ if (!bindGestureData.el) {
714
+ return;
715
+ }
716
+ const speed: number = 6;
717
+ const gestureElement = document.getElementById('cg-gesture')!;
718
+ const dirs = opt.dirs ?? ['top', 'bottom'];
719
+
720
+ if (bindGestureData.tx > bindGestureData.xx) {
721
+ bindGestureData.xx += speed;
722
+ if (bindGestureData.xx > bindGestureData.tx) {
723
+ bindGestureData.xx = bindGestureData.tx;
724
+ }
725
+ }
726
+ else {
727
+ bindGestureData.xx -= speed;
728
+ if (bindGestureData.xx < bindGestureData.tx) {
729
+ bindGestureData.xx = bindGestureData.tx;
730
+ }
731
+ }
732
+ if (bindGestureData.ty > bindGestureData.xy) {
733
+ bindGestureData.xy += speed;
734
+ if (bindGestureData.xy > bindGestureData.ty) {
735
+ bindGestureData.xy = bindGestureData.ty;
736
+ }
737
+ }
738
+ else {
739
+ bindGestureData.xy -= speed;
740
+ if (bindGestureData.xy < bindGestureData.ty) {
741
+ bindGestureData.xy = bindGestureData.ty;
742
+ }
743
+ }
744
+ const xxAbs = Math.abs(bindGestureData.xx);
745
+ const xyAbs = Math.abs(bindGestureData.xy);
746
+ if ((!dirs.includes('top') && !dirs.includes('bottom')) || ((xxAbs > xyAbs) && (dirs.includes('left') || dirs.includes('right')))) {
747
+ // --- 水平 ---
748
+ if (bindGestureData.xx < 0) {
749
+ // --- left ---
750
+ if (!dirs.includes('left')) {
751
+ gestureElement.style.opacity = '0';
752
+ clearGestureData();
753
+ return;
754
+ }
755
+ gestureElement.style.opacity = '1';
756
+ if (xxAbs === 90) {
757
+ bindGestureData.dir = 'left';
758
+ gestureElement.classList.add('done');
759
+ }
760
+ else {
761
+ bindGestureData.dir = null;
762
+ gestureElement.classList.remove('done');
763
+ }
764
+ gestureElement.style.top = (opt.rect.top + ((opt.rect.height - 20) / 2)).toString() + 'px';
765
+ gestureElement.style.left = (opt.rect.left - 10 + (xxAbs / 1.5)).toString() + 'px';
766
+ gestureElement.style.transform = 'scale(' + (xxAbs / 90).toString() + ')';
767
+ }
768
+ else {
769
+ // --- right ---
770
+ if (!dirs.includes('right')) {
771
+ gestureElement.style.opacity = '0';
772
+ clearGestureData();
773
+ return;
774
+ }
775
+ gestureElement.style.opacity = '1';
776
+ if (xxAbs === 90) {
777
+ bindGestureData.dir = 'right';
778
+ gestureElement.classList.add('done');
779
+ }
780
+ else {
781
+ bindGestureData.dir = null;
782
+ gestureElement.classList.remove('done');
783
+ }
784
+ gestureElement.style.top = (opt.rect.top + ((opt.rect.height - 20) / 2)).toString() + 'px';
785
+ gestureElement.style.left = (opt.rect.left + opt.rect.width - 10 - (xxAbs / 1.5)).toString() + 'px';
786
+ gestureElement.style.transform = 'scale(' + (xxAbs / 90).toString() + ')';
787
+ }
788
+ }
789
+ else {
790
+ if (bindGestureData.xy < 0) {
791
+ // --- top ---
792
+ if (!dirs.includes('top')) {
793
+ gestureElement.style.opacity = '0';
794
+ clearGestureData();
795
+ return;
796
+ }
797
+ gestureElement.style.opacity = '1';
798
+ if (xyAbs === 90) {
799
+ bindGestureData.dir = 'top';
800
+ gestureElement.classList.add('done');
801
+ }
802
+ else {
803
+ bindGestureData.dir = null;
804
+ gestureElement.classList.remove('done');
805
+ }
806
+ gestureElement.style.left = (opt.rect.left + ((opt.rect.width - 20) / 2)).toString() + 'px';
807
+ gestureElement.style.top = (opt.rect.top - 10 + (xyAbs / 1.5)).toString() + 'px';
808
+ gestureElement.style.transform = 'scale(' + (xyAbs / 90).toString() + ')';
809
+ }
810
+ else {
811
+ // --- bottom ---
812
+ if (!dirs.includes('bottom')) {
813
+ gestureElement.style.opacity = '0';
814
+ clearGestureData();
815
+ return;
816
+ }
817
+ gestureElement.style.opacity = '1';
818
+ if (xyAbs === 90) {
819
+ bindGestureData.dir = 'bottom';
820
+ gestureElement.classList.add('done');
821
+ }
822
+ else {
823
+ bindGestureData.dir = null;
824
+ gestureElement.classList.remove('done');
825
+ }
826
+ gestureElement.style.left = (opt.rect.left + ((opt.rect.width - 20) / 2)).toString() + 'px';
827
+ gestureElement.style.top = (opt.rect.top + opt.rect.height - 10 - (xyAbs / 1.5)).toString() + 'px';
828
+ gestureElement.style.transform = 'scale(' + (xyAbs / 90).toString() + ')';
829
+ }
830
+ }
831
+
832
+ if (bindGestureData.xx === bindGestureData.tx && bindGestureData.xy === bindGestureData.ty) {
833
+ // --- 中途终止 ---
834
+ bindGestureData.timers.ani = 0;
835
+ bindGestureData.timers.sleep = window.setTimeout(() => {
836
+ // --- 触摸板 wheel 可能会多次执行,导致圆圈一直在,有缓动 ---
837
+ clearGestureData();
838
+ bindGestureData.timers.sleep = 0;
839
+ gestureElement.style.opacity = '0';
840
+ if (!bindGestureData.dir) {
841
+ return;
842
+ }
843
+ opt.handler(bindGestureData.dir);
844
+ }, 500);
845
+ return;
846
+ }
847
+ bindGestureData.timers.ani = requestAnimationFrame(() => {
848
+ bindGestureAnimation(opt);
849
+ });
850
+ }
851
+
852
+ /**
853
+ * --- 绑定上拉、下拉、左拉、右拉 ---
854
+ * @param e 响应事件
855
+ * @param opt 选项
856
+ */
857
+ export function bindGesture(e: MouseEvent | TouchEvent | WheelEvent | { 'x'?: number; 'y'?: number; }, opt: types.IBindGestureOptions): void {
858
+ const gestureElement = document.getElementById('cg-gesture')!;
859
+ const el: HTMLElement | undefined = ((e as any).currentTarget as HTMLElement | undefined)
860
+ ?? ((e as any).target as HTMLElement | undefined) ?? opt.el;
861
+ if (!el) {
862
+ return;
863
+ }
864
+ let rect: DOMRect;
865
+ if ((e as any).rect) {
866
+ rect = (e as any).rect as DOMRect;
867
+ }
868
+ else if (opt.rect) {
869
+ rect = opt.rect;
870
+ }
871
+ else {
872
+ if (!(el.getBoundingClientRect)) {
873
+ return;
874
+ }
875
+ rect = el.getBoundingClientRect();
876
+ }
877
+ const dirs = opt.dirs ?? ['top', 'bottom'];
878
+ if ((e instanceof MouseEvent || e instanceof TouchEvent) && !(e instanceof WheelEvent)) {
879
+ // --- touch / mouse 触发的 ---
880
+ const x: number = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
881
+ const y: number = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
882
+ let dir: 'top' | 'right' | 'bottom' | 'left' | null = null;
883
+ /** --- 当前手势方向 --- */
884
+ bindDown(e, {
885
+ move: (e) => {
886
+ e.preventDefault();
887
+ if (bindGestureData.timers.ani !== 0) {
888
+ cancelAnimationFrame(bindGestureData.timers.ani);
889
+ bindGestureData.timers.ani = 0;
890
+ }
891
+ if (bindGestureData.timers.sleep !== 0) {
892
+ clearTimeout(bindGestureData.timers.sleep);
893
+ bindGestureData.timers.sleep = 0;
894
+ }
895
+
896
+ const nx: number = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
897
+ const ny: number = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
898
+ /** --- 相对于按下时 x 的差值 --- */
899
+ const xx = nx - x;
900
+ /** --- 相对于按下时 y 的差值 --- */
901
+ const xy = ny - y;
902
+ let xxAbs = Math.abs(xx);
903
+ let xyAbs = Math.abs(xy);
904
+ if ((!dirs.includes('top') && !dirs.includes('bottom')) || ((xxAbs > xyAbs) && (dirs.includes('left') || dirs.includes('right')))) {
905
+ // --- 水平 ---
906
+ if (xx > 0) {
907
+ // --- left ---
908
+ if (!dirs.includes('left')) {
909
+ gestureElement.style.opacity = '0';
910
+ return;
911
+ }
912
+ gestureElement.style.opacity = '1';
913
+ if (xxAbs > 90) {
914
+ xxAbs = 90;
915
+ dir = 'left';
916
+ gestureElement.classList.add('done');
917
+ }
918
+ else {
919
+ dir = null;
920
+ gestureElement.classList.remove('done');
921
+ }
922
+ gestureElement.style.top = (rect.top + ((rect.height - 20) / 2)).toString() + 'px';
923
+ gestureElement.style.left = (rect.left - 10 + (xxAbs / 1.5)).toString() + 'px';
924
+ gestureElement.style.transform = 'scale(' + (xxAbs / 90).toString() + ')';
925
+ }
926
+ else {
927
+ // --- right ---
928
+ if (!dirs.includes('right')) {
929
+ gestureElement.style.opacity = '0';
930
+ return;
931
+ }
932
+ gestureElement.style.opacity = '1';
933
+ if (xxAbs > 90) {
934
+ xxAbs = 90;
935
+ dir = 'right';
936
+ gestureElement.classList.add('done');
937
+ }
938
+ else {
939
+ dir = null;
940
+ gestureElement.classList.remove('done');
941
+ }
942
+ gestureElement.style.top = (rect.top + ((rect.height - 20) / 2)).toString() + 'px';
943
+ gestureElement.style.left = (rect.left + rect.width - 10 - (xxAbs / 1.5)).toString() + 'px';
944
+ gestureElement.style.transform = 'scale(' + (xxAbs / 90).toString() + ')';
945
+ }
946
+ }
947
+ else {
948
+ if (xy > 0) {
949
+ // --- top ---
950
+ if (!dirs.includes('top')) {
951
+ gestureElement.style.opacity = '0';
952
+ return;
953
+ }
954
+ gestureElement.style.opacity = '1';
955
+ if (xyAbs > 90) {
956
+ xyAbs = 90;
957
+ dir = 'top';
958
+ gestureElement.classList.add('done');
959
+ }
960
+ else {
961
+ dir = null;
962
+ gestureElement.classList.remove('done');
963
+ }
964
+ gestureElement.style.left = (rect.left + ((rect.width - 20) / 2)).toString() + 'px';
965
+ gestureElement.style.top = (rect.top - 10 + (xyAbs / 1.5)).toString() + 'px';
966
+ gestureElement.style.transform = 'scale(' + (xyAbs / 90).toString() + ')';
967
+ }
968
+ else {
969
+ // --- bottom ---
970
+ if (!dirs.includes('bottom')) {
971
+ gestureElement.style.opacity = '0';
972
+ return;
973
+ }
974
+ gestureElement.style.opacity = '1';
975
+ if (xyAbs > 90) {
976
+ xyAbs = 90;
977
+ dir = 'bottom';
978
+ gestureElement.classList.add('done');
979
+ }
980
+ else {
981
+ dir = null;
982
+ gestureElement.classList.remove('done');
983
+ }
984
+ gestureElement.style.left = (rect.left + ((rect.width - 20) / 2)).toString() + 'px';
985
+ gestureElement.style.top = (rect.top + rect.height - 10 - (xyAbs / 1.5)).toString() + 'px';
986
+ gestureElement.style.transform = 'scale(' + (xyAbs / 90).toString() + ')';
987
+ }
988
+ }
989
+ },
990
+ end: (): void => {
991
+ gestureElement.style.opacity = '0';
992
+ if (!dir) {
993
+ return;
994
+ }
995
+ opt.handler(dir);
996
+ }
997
+ });
998
+ }
999
+ else {
1000
+ // --- wheel 触发、自定义触发 ---
1001
+ if (bindGestureData.el !== el) {
1002
+ bindGestureData.el = el;
1003
+ bindGestureData.xx = 0;
1004
+ bindGestureData.xy = 0;
1005
+ }
1006
+ let x: number = 0, y: number = 0;
1007
+ if (e instanceof WheelEvent) {
1008
+ e.preventDefault();
1009
+ if (Math.abs(e.deltaX) < 5 && Math.abs(e.deltaY) < 5) {
1010
+ return;
1011
+ }
1012
+ x = Math.round(e.deltaX / 3);
1013
+ y = Math.round(e.deltaY / 3);
1014
+ if ((e as any).direction === 'h') {
1015
+ x = y;
1016
+ y = 0;
1017
+ }
1018
+ else if ((e as any).direction === 'v') {
1019
+ y = x;
1020
+ x = 0;
1021
+ }
1022
+ }
1023
+ else {
1024
+ x = e.x ?? 0;
1025
+ y = e.y ?? 0;
1026
+ }
1027
+ let tx = bindGestureData.tx + x;
1028
+ if (tx > 90) {
1029
+ tx = 90;
1030
+ }
1031
+ else if (tx < -90) {
1032
+ tx = -90;
1033
+ }
1034
+ let ty = bindGestureData.ty + y;
1035
+ if (ty > 90) {
1036
+ ty = 90;
1037
+ }
1038
+ else if (ty < -90) {
1039
+ ty = -90;
1040
+ }
1041
+ bindGestureData.tx = tx;
1042
+ bindGestureData.ty = ty;
1043
+ if (bindGestureData.timers.ani !== 0) {
1044
+ cancelAnimationFrame(bindGestureData.timers.ani);
1045
+ bindGestureData.timers.ani = 0;
1046
+ }
1047
+ if (bindGestureData.timers.sleep !== 0) {
1048
+ clearTimeout(bindGestureData.timers.sleep);
1049
+ bindGestureData.timers.sleep = 0;
1050
+ }
1051
+ bindGestureAnimation({
1052
+ 'rect': rect,
1053
+ 'dirs': opt.dirs,
1054
+ 'handler': opt.handler
1055
+ });
1056
+ }
1057
+ }
1058
+
1059
+ let lastLongTime: number = 0;
1060
+
1061
+ export function allowEvent(e: MouseEvent | TouchEvent | KeyboardEvent): boolean {
1062
+ const now = Date.now();
1063
+ if (now - lastLongTime < 5) {
1064
+ return false;
1065
+ }
1066
+ const current = e.currentTarget as HTMLElement;
1067
+ if (current.dataset.cgDisabled !== undefined) {
1068
+ return false;
1069
+ }
1070
+ if (findParentByData(current, 'cg-disabled')) {
1071
+ return false;
1072
+ }
1073
+ return true;
1074
+ }
1075
+
1076
+ /**
1077
+ * --- 绑定长按事件 ---
1078
+ * @param e 事件原型
1079
+ * @param long 回调
1080
+ */
1081
+ export function bindLong(e: MouseEvent | TouchEvent, long: (e: MouseEvent | TouchEvent) => void | Promise<void>): void {
1082
+ if (hasTouchButMouse(e)) {
1083
+ return;
1084
+ }
1085
+ /** --- 上一次的坐标 --- */
1086
+ const tx: number = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
1087
+ const ty: number = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
1088
+ let ox: number = 0;
1089
+ let oy: number = 0;
1090
+ /** --- 是否执行了 long --- */
1091
+ let isLong: boolean = false;
1092
+ let timer: number | undefined = window.setTimeout(() => {
1093
+ clearTimeout(timer);
1094
+ timer = undefined;
1095
+ if (ox <= 1 && oy <= 1) {
1096
+ isLong = true;
1097
+ const rtn = long(e);
1098
+ if (rtn instanceof Promise) {
1099
+ rtn.catch((e) => {
1100
+ throw e;
1101
+ });
1102
+ }
1103
+ }
1104
+ }, 300);
1105
+ bindDown(e, {
1106
+ move: (e: MouseEvent | TouchEvent) => {
1107
+ const x: number = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
1108
+ const y: number = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
1109
+ ox = Math.abs(x - tx);
1110
+ oy = Math.abs(y - ty);
1111
+ },
1112
+ up: () => {
1113
+ if (timer !== undefined) {
1114
+ // --- 肯定没执行 long ---
1115
+ clearTimeout(timer);
1116
+ timer = undefined;
1117
+ }
1118
+ else if (isLong) {
1119
+ lastLongTime = Date.now();
1120
+ }
1121
+ }
1122
+ });
1123
+ }
1124
+
1125
+ /**
1126
+ * --- 绑定拖动 ---
1127
+ * @param e 鼠标事件
1128
+ * @param opt 参数
1129
+ */
1130
+ export function bindDrag(e: MouseEvent | TouchEvent, opt: { 'el': HTMLElement; 'data'?: any; }): void {
1131
+ let otop = 0;
1132
+ let oleft = 0;
1133
+ let nel: HTMLElement | null = null;
1134
+ bindMove(e, {
1135
+ 'object': opt.el,
1136
+ 'start': function() {
1137
+ const rect = opt.el.getBoundingClientRect();
1138
+ form.showDrag();
1139
+ form.moveDrag({
1140
+ 'top': rect.top,
1141
+ 'left': rect.left,
1142
+ 'width': rect.width,
1143
+ 'height': rect.height,
1144
+ 'icon': true
1145
+ });
1146
+ otop = rect.top;
1147
+ oleft = rect.left;
1148
+ },
1149
+ 'move': function(ox, oy, x, y) {
1150
+ const ntop = otop + oy;
1151
+ const nleft = oleft + ox;
1152
+ form.moveDrag({
1153
+ 'top': ntop,
1154
+ 'left': nleft,
1155
+ 'icon': false
1156
+ });
1157
+ otop = ntop;
1158
+ oleft = nleft;
1159
+ // --- 获取当前 element ---
1160
+ const els = document.elementsFromPoint(x, y) as HTMLElement[];
1161
+ for (const el of els) {
1162
+ if (el.dataset.cgDrop === undefined) {
1163
+ continue;
1164
+ }
1165
+ if (el === opt.el) {
1166
+ continue;
1167
+ }
1168
+ if (el === nel) {
1169
+ // --- 还是当前的 ---
1170
+ return;
1171
+ }
1172
+ if (nel !== null) {
1173
+ nel.removeAttribute('data-cg-hover');
1174
+ nel.dispatchEvent(new CustomEvent('dragleave', {
1175
+ 'detail': {
1176
+ 'value': opt.data
1177
+ }
1178
+ }));
1179
+ }
1180
+ el.dataset.cgHover = '';
1181
+ nel = el;
1182
+ nel.dispatchEvent(new CustomEvent('dragenter', {
1183
+ 'detail': {
1184
+ 'value': opt.data
1185
+ }
1186
+ }));
1187
+ return;
1188
+ }
1189
+ // --- not found ---
1190
+ form.moveDrag({
1191
+ 'icon': true
1192
+ });
1193
+ if (nel === null) {
1194
+ return;
1195
+ }
1196
+ nel.removeAttribute('data-cg-hover');
1197
+ nel.dispatchEvent(new CustomEvent('dragleave', {
1198
+ 'detail': {
1199
+ 'value': opt.data
1200
+ }
1201
+ }));
1202
+ nel = null;
1203
+ },
1204
+ 'end': function() {
1205
+ form.hideDrag();
1206
+ if (nel === null) {
1207
+ return;
1208
+ }
1209
+ nel.removeAttribute('data-cg-hover');
1210
+ nel.dispatchEvent(new CustomEvent('drop', {
1211
+ 'detail': {
1212
+ 'value': opt.data
1213
+ }
1214
+ }));
1215
+ }
1216
+ });
1217
+ }
1218
+
1219
+ /** --- 目前是否已绑定了 bindMove --- */
1220
+ export const is = Vue.reactive({
1221
+ 'move': false,
1222
+ 'shift': false,
1223
+ 'ctrl': false
1224
+ });
1225
+
1226
+ window.addEventListener('keydown', function(e: KeyboardEvent) {
1227
+ switch (e.key) {
1228
+ case 'Shift': {
1229
+ is.shift = true;
1230
+ break;
1231
+ }
1232
+ case 'Control': {
1233
+ is.ctrl = true;
1234
+ break;
1235
+ }
1236
+ }
1237
+ });
1238
+ window.addEventListener('keyup', function(e: KeyboardEvent) {
1239
+ switch (e.key) {
1240
+ case 'Shift': {
1241
+ is.shift = false;
1242
+ break;
1243
+ }
1244
+ case 'Control': {
1245
+ is.ctrl = false;
1246
+ break;
1247
+ }
1248
+ }
1249
+ });
1250
+
1251
+ /**
1252
+ * --- 绑定拖动事件 ---
1253
+ * @param e mousedown 或 touchstart 的 event
1254
+ * @param opt 回调选项
1255
+ */
1256
+ export function bindMove(e: MouseEvent | TouchEvent, opt: types.IBindMoveOptions): types.IBindMoveResult {
1257
+ if (hasTouchButMouse(e)) {
1258
+ return {
1259
+ 'left': 0,
1260
+ 'top': 0,
1261
+ 'right': 0,
1262
+ 'bottom': 0
1263
+ };
1264
+ }
1265
+ is.move = true;
1266
+ setGlobalCursor(getComputedStyle(e.target as Element).cursor);
1267
+ /** --- 上一次的 x 坐标 --- */
1268
+ let tx: number = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
1269
+ /** --- 上一次的 y 坐标 --- */
1270
+ let ty: number = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
1271
+
1272
+ /** --- 拖动限定区域左侧 --- */
1273
+ let left: number,
1274
+ /** --- 拖动限定区域顶部 --- */
1275
+ top: number,
1276
+ /** --- 拖动限定区域右侧 --- */
1277
+ right: number,
1278
+ /** --- 拖动限定区域底部 --- */
1279
+ bottom: number;
1280
+ if (opt.areaObject) {
1281
+ if (!(opt.areaObject instanceof HTMLElement)) {
1282
+ opt.areaObject = opt.areaObject.$el;
1283
+ }
1284
+ const areaRect = opt.areaObject.getBoundingClientRect();
1285
+ const areaStyle = getComputedStyle(opt.areaObject);
1286
+ left = areaRect.left + parseFloat(areaStyle.borderLeftWidth) + parseFloat(areaStyle.paddingLeft);
1287
+ top = areaRect.top + parseFloat(areaStyle.borderTopWidth) + parseFloat(areaStyle.paddingTop);
1288
+ right = areaRect.left + areaRect.width - (parseFloat(areaStyle.borderRightWidth)
1289
+ + parseFloat(areaStyle.paddingRight));
1290
+ bottom = areaRect.top + areaRect.height - (parseFloat(areaStyle.borderRightWidth)
1291
+ + parseFloat(areaStyle.paddingRight));
1292
+ }
1293
+ else {
1294
+ const area = core.getAvailArea();
1295
+ left = opt.left ?? area.left;
1296
+ top = opt.top ?? area.top;
1297
+ right = opt.right ?? area.width;
1298
+ bottom = opt.bottom ?? area.height;
1299
+ }
1300
+ // --- 限定拖动区域额外补偿(拖动对象和实际对象有一定偏差时使用) ---
1301
+ if (opt.offsetLeft) {
1302
+ left += opt.offsetLeft;
1303
+ }
1304
+ if (opt.offsetTop) {
1305
+ top += opt.offsetTop;
1306
+ }
1307
+ if (opt.offsetRight) {
1308
+ right += opt.offsetRight;
1309
+ }
1310
+ if (opt.offsetBottom) {
1311
+ bottom += opt.offsetBottom;
1312
+ }
1313
+
1314
+ /** --- 判断是否已经到达了边界 --- */
1315
+ let isBorder: boolean = false;
1316
+
1317
+ // --- 限定拖动对象,限定后整体对象将无法拖动出边界 ---
1318
+ let objectLeft: number, objectTop: number, objectWidth: number, objectHeight: number;
1319
+ /** --- 初始坐标距离 object 左侧的距离 --- */
1320
+ let offsetLeft: number = 0,
1321
+ /** --- 初始坐标距离 object 顶部的距离 --- */
1322
+ offsetTop: number = 0,
1323
+ /** --- 初始坐标距离 object 右侧的距离 --- */
1324
+ offsetRight: number = 0,
1325
+ /** --- 初始坐标距离 object 底部的距离 --- */
1326
+ offsetBottom = 0;
1327
+
1328
+ /** --- 每次拖动时的时间以及偏移 --- */
1329
+ const moveTimes: Array<{ 'time': number; 'ox': number; 'oy': number; }> = [];
1330
+
1331
+ bindDown(e, {
1332
+ start: () => {
1333
+ if (opt.start) {
1334
+ if (opt.start(tx, ty) === false) {
1335
+ setGlobalCursor();
1336
+ return false;
1337
+ }
1338
+ }
1339
+ // --- 限定拖动对象,限定后整体对象将无法拖动出边界 ---
1340
+ if (opt.object) {
1341
+ if (!(opt.object instanceof HTMLElement)) {
1342
+ opt.object = opt.object.$el;
1343
+ }
1344
+ const rect = opt.object.getBoundingClientRect();
1345
+ objectLeft = rect.left;
1346
+ objectTop = rect.top;
1347
+ objectWidth = rect.width;
1348
+ objectHeight = rect.height;
1349
+ }
1350
+ else {
1351
+ objectLeft = opt.objectLeft ?? 0;
1352
+ objectTop = opt.objectTop ?? 0;
1353
+ objectWidth = opt.objectWidth ?? 0;
1354
+ objectHeight = opt.objectHeight ?? 0;
1355
+ }
1356
+
1357
+ // --- 限定边界的偏移,如果限定了拖动对象,则需要根据偏移来判断边界,毕竟拖动点不可能每次都刚好是边界 ---
1358
+ if (objectWidth > 0) {
1359
+ offsetLeft = tx - objectLeft;
1360
+ }
1361
+ if (objectHeight > 0) {
1362
+ offsetTop = ty - objectTop;
1363
+ }
1364
+ offsetRight = objectWidth - offsetLeft;
1365
+ offsetBottom = objectHeight - offsetTop;
1366
+ },
1367
+ move: (e: MouseEvent | TouchEvent, dir) => {
1368
+ /** --- 本次 x 坐标 --- */
1369
+ let x: number,
1370
+ /** --- 本次 y 坐标 --- */
1371
+ y: number;
1372
+ x = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
1373
+ y = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
1374
+ if (x === tx && y === ty) {
1375
+ // --- 没有移动,直接返回 ---
1376
+ return;
1377
+ }
1378
+
1379
+ /** --- 当前是否在顶部边界线上 --- */
1380
+ let inBorderTop: boolean = false,
1381
+ /** --- 当前是否在右侧边界上 --- */
1382
+ inBorderRight: boolean = false,
1383
+ /** --- 当前是否在底部边界上 --- */
1384
+ inBorderBottom: boolean = false,
1385
+ /** --- 当前是否在左侧边界上 --- */
1386
+ inBorderLeft: boolean = false;
1387
+
1388
+ /** --- 当前理论上可拖动 object 应该存在的 x 左侧 --- */
1389
+ const nowLeft = x - offsetLeft;
1390
+ /** --- 当前理论上可拖动 object 应该存在的 x 右侧 --- */
1391
+ const nowRight = x + offsetRight;
1392
+ if (nowLeft <= left) {
1393
+ // --- 必定是到左侧边界了 ---
1394
+ inBorderLeft = true;
1395
+ if (nowLeft < left && x < tx) {
1396
+ // --- 当前 x 超越了 left 界限,还在向左移动 ---
1397
+ if (tx - offsetLeft > left) {
1398
+ // --- 如果刚刚还没超过,则设定为界限值 ---
1399
+ x = left + offsetLeft;
1400
+ }
1401
+ else {
1402
+ // --- 如果刚刚就已经超过了,则恢复成刚刚 ---
1403
+ x = tx;
1404
+ }
1405
+ }
1406
+ }
1407
+ else if (offsetRight !== 0) {
1408
+ if (nowRight >= right) {
1409
+ // --- 必定到右侧边界 ---
1410
+ inBorderRight = true;
1411
+ if (nowRight > right && x > tx) {
1412
+ if (tx + offsetRight < right) {
1413
+ x = right - offsetRight;
1414
+ }
1415
+ else {
1416
+ x = tx;
1417
+ }
1418
+ }
1419
+ }
1420
+ }
1421
+ else if (offsetRight === 0) {
1422
+ const r1 = right - 1;
1423
+ if (x >= r1) {
1424
+ inBorderRight = true;
1425
+ if (x > r1 && x > tx) {
1426
+ if (tx < r1) {
1427
+ x = r1;
1428
+ }
1429
+ else {
1430
+ x = tx;
1431
+ }
1432
+ }
1433
+ }
1434
+ }
1435
+
1436
+ /** --- 当前理论上可拖动 object 应该存在的 y 顶部 --- */
1437
+ const nowTop = y - offsetTop;
1438
+ /** --- 当前理论上可拖动 object 应该存在的 y 底部 --- */
1439
+ const nowBottom = y + offsetBottom;
1440
+ if (nowTop <= top) {
1441
+ inBorderTop = true;
1442
+ if (nowTop < top && y < ty) {
1443
+ if (ty - offsetTop > top) {
1444
+ y = top + offsetTop;
1445
+ }
1446
+ else {
1447
+ y = ty;
1448
+ }
1449
+ }
1450
+ }
1451
+ else if (offsetBottom !== 0) {
1452
+ if (nowBottom >= bottom) {
1453
+ inBorderBottom = true;
1454
+ if (nowBottom > bottom && y > ty) {
1455
+ if (ty + offsetBottom < bottom) {
1456
+ y = bottom - offsetBottom;
1457
+ }
1458
+ else {
1459
+ y = ty;
1460
+ }
1461
+ }
1462
+ }
1463
+ }
1464
+ else if (offsetBottom === 0) {
1465
+ const b1 = bottom - 1;
1466
+ if (y >= b1) {
1467
+ inBorderBottom = true;
1468
+ if (y > b1 && y > ty) {
1469
+ if (ty < b1) {
1470
+ y = b1;
1471
+ }
1472
+ else {
1473
+ y = ty;
1474
+ }
1475
+ }
1476
+ }
1477
+ }
1478
+
1479
+ // --- 检测是否执行 borderIn 事件(是否正在边界上) ---
1480
+ let border: types.TBorder = '';
1481
+ if (inBorderTop || inBorderRight || inBorderBottom || inBorderLeft) {
1482
+ if (inBorderTop) {
1483
+ if (x - left <= 20) {
1484
+ border = 'lt';
1485
+ }
1486
+ else if (right - x <= 20) {
1487
+ border = 'tr';
1488
+ }
1489
+ else {
1490
+ border = 't';
1491
+ }
1492
+ }
1493
+ else if (inBorderRight) {
1494
+ if (y - top <= 20) {
1495
+ border = 'tr';
1496
+ }
1497
+ else if (bottom - y <= 20) {
1498
+ border = 'rb';
1499
+ }
1500
+ else {
1501
+ border = 'r';
1502
+ }
1503
+ }
1504
+ else if (inBorderBottom) {
1505
+ if (right - x <= 20) {
1506
+ border = 'rb';
1507
+ }
1508
+ else if (x - left <= 20) {
1509
+ border = 'bl';
1510
+ }
1511
+ else {
1512
+ border = 'b';
1513
+ }
1514
+ }
1515
+ else if (inBorderLeft) {
1516
+ if (y - top <= 20) {
1517
+ border = 'lt';
1518
+ }
1519
+ else if (bottom - y <= 20) {
1520
+ border = 'bl';
1521
+ }
1522
+ else {
1523
+ border = 'l';
1524
+ }
1525
+ }
1526
+
1527
+ if (!isBorder) {
1528
+ isBorder = true;
1529
+ opt.borderIn?.(x, y, border, e);
1530
+ }
1531
+ }
1532
+ else {
1533
+ // --- 不在边界 ---
1534
+ if (isBorder) {
1535
+ isBorder = false;
1536
+ opt.borderOut?.();
1537
+ }
1538
+ }
1539
+
1540
+ const ox = x - tx;
1541
+ const oy = y - ty;
1542
+ moveTimes.push({
1543
+ 'time': Date.now(),
1544
+ 'ox': ox,
1545
+ 'oy': oy
1546
+ });
1547
+
1548
+ opt.move?.(ox, oy, x, y, border, dir, e);
1549
+ tx = x;
1550
+ ty = y;
1551
+ },
1552
+ up: (e) => {
1553
+ is.move = false;
1554
+ setGlobalCursor();
1555
+ opt.up?.(moveTimes, e);
1556
+ },
1557
+ end: (e) => {
1558
+ opt.end?.(moveTimes, e);
1559
+ }
1560
+ });
1561
+
1562
+ if (opt.showRect) {
1563
+ form.showRectangle(tx, ty, {
1564
+ 'left': left,
1565
+ 'top': top,
1566
+ 'width': right - left,
1567
+ 'height': bottom - top
1568
+ });
1569
+ setTimeout(() => {
1570
+ form.hideRectangle();
1571
+ }, 3000);
1572
+ }
1573
+
1574
+ return {
1575
+ 'left': left,
1576
+ 'top': top,
1577
+ 'right': right,
1578
+ 'bottom': bottom
1579
+ };
1580
+ }
1581
+
1582
+ /**
1583
+ * --- 绑定拖动改变大小事件 ---
1584
+ * @param e mousedown 或 touchstart 的 event
1585
+ * @param opt 选项,width, height 当前对象宽高
1586
+ * @param moveCb 拖动时的回调
1587
+ * @param endCb 结束时的回调
1588
+ */
1589
+ export function bindResize(e: MouseEvent | TouchEvent, opt: types.IBindResizeOptions): void {
1590
+ if (hasTouchButMouse(e)) {
1591
+ return;
1592
+ }
1593
+ opt.minWidth = opt.minWidth ?? 0;
1594
+ opt.minHeight = opt.minHeight ?? 0;
1595
+ /** --- 当前鼠标位置 x --- */
1596
+ const x: number = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
1597
+ /** --- 当前鼠标位置 y --- */
1598
+ const y: number = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
1599
+ // --- 获取偏差补偿 ---
1600
+ let offsetLeft!: number, offsetTop!: number, offsetRight!: number, offsetBottom!: number;
1601
+ /** --- 上下左右界限 --- */
1602
+ let left!: number, top!: number, right!: number, bottom!: number;
1603
+
1604
+ // --- 获取 object 的 x,y 和 w,h 信息 ---
1605
+ if (opt.objectLeft === undefined
1606
+ || opt.objectTop === undefined
1607
+ || opt.objectWidth === undefined
1608
+ || opt.objectHeight === undefined
1609
+ ) {
1610
+ if (!opt.object) {
1611
+ return;
1612
+ }
1613
+ if (!(opt.object instanceof HTMLElement)) {
1614
+ opt.object = opt.object.$el;
1615
+ }
1616
+ const objectRect = opt.object.getBoundingClientRect();
1617
+ opt.objectLeft = objectRect.left;
1618
+ opt.objectTop = objectRect.top;
1619
+ opt.objectWidth = objectRect.width;
1620
+ opt.objectHeight = objectRect.height;
1621
+ }
1622
+
1623
+ if (opt.border === 'tr' || opt.border === 'r' || opt.border === 'rb') {
1624
+ // --- ↗、→、↘ ---
1625
+ left = opt.objectLeft + opt.minWidth;
1626
+ offsetLeft = x - (opt.objectLeft + opt.objectWidth);
1627
+ offsetRight = offsetLeft;
1628
+ if (opt.maxWidth) {
1629
+ right = opt.objectLeft + opt.maxWidth;
1630
+ }
1631
+ }
1632
+ else if (opt.border === 'bl' || opt.border === 'l' || opt.border === 'lt') {
1633
+ // --- ↙、←、↖ ---
1634
+ right = opt.objectLeft + opt.objectWidth - opt.minWidth;
1635
+ offsetLeft = x - opt.objectLeft;
1636
+ offsetRight = offsetLeft;
1637
+ if (opt.maxWidth) {
1638
+ left = opt.objectLeft + opt.objectWidth - opt.maxWidth;
1639
+ }
1640
+ }
1641
+ if (opt.border === 'rb' || opt.border === 'b' || opt.border === 'bl') {
1642
+ // --- ↘、↓、↙ ---
1643
+ top = opt.objectTop + opt.minHeight;
1644
+ offsetTop = y - (opt.objectTop + opt.objectHeight);
1645
+ offsetBottom = offsetTop;
1646
+ if (opt.maxHeight) {
1647
+ bottom = opt.objectTop + opt.maxHeight;
1648
+ }
1649
+ }
1650
+ else if (opt.border === 'lt' || opt.border === 't' || opt.border === 'tr') {
1651
+ bottom = opt.objectTop + opt.objectHeight - opt.minHeight;
1652
+ offsetTop = y - opt.objectTop;
1653
+ offsetBottom = offsetTop;
1654
+ if (opt.maxHeight) {
1655
+ top = opt.objectTop + opt.objectHeight - opt.maxHeight;
1656
+ }
1657
+ }
1658
+ bindMove(e, {
1659
+ 'left': left,
1660
+ 'top': top,
1661
+ 'right': right,
1662
+ 'bottom': bottom,
1663
+ 'offsetLeft': offsetLeft,
1664
+ 'offsetTop': offsetTop,
1665
+ 'offsetRight': offsetRight,
1666
+ 'offsetBottom': offsetBottom,
1667
+ 'start': opt.start,
1668
+ 'move': function(ox, oy, x, y, border) {
1669
+ if (opt.border === 'tr' || opt.border === 'r' || opt.border === 'rb') {
1670
+ opt.objectWidth! += ox;
1671
+ }
1672
+ else if (opt.border === 'bl' || opt.border === 'l' || opt.border === 'lt') {
1673
+ opt.objectWidth! -= ox;
1674
+ opt.objectLeft! += ox;
1675
+ }
1676
+ if (opt.border === 'rb' || opt.border === 'b' || opt.border === 'bl') {
1677
+ opt.objectHeight! += oy;
1678
+ }
1679
+ else if (opt.border === 'lt' || opt.border === 't' || opt.border === 'tr') {
1680
+ opt.objectHeight! -= oy;
1681
+ opt.objectTop! += oy;
1682
+ }
1683
+ opt.move?.(opt.objectLeft!, opt.objectTop!, opt.objectWidth!, opt.objectHeight!, x, y, border);
1684
+ },
1685
+ 'end': opt.end
1686
+ });
1687
+ }
1688
+
1689
+ /**
1690
+ * --- 通过 data 名查找上层所有标签是否存在 ---
1691
+ * @param el 当前标签
1692
+ * @param name 要查找的 data 名
1693
+ */
1694
+ export function findParentByData(el: HTMLElement, name: string): HTMLElement | null {
1695
+ let parent = el.parentNode as HTMLElement;
1696
+ while (parent) {
1697
+ if (!parent.tagName) {
1698
+ continue;
1699
+ }
1700
+ if (parent.tagName.toLowerCase() === 'body') {
1701
+ break;
1702
+ }
1703
+ if (parent.getAttribute('data-' + name) !== null) {
1704
+ return parent;
1705
+ }
1706
+ parent = parent.parentNode as HTMLElement;
1707
+ }
1708
+ return null;
1709
+ }
1710
+
1711
+ /**
1712
+ * --- 通过 class 名查找上层所有标签是否存在 ---
1713
+ * @param el 当前标签
1714
+ * @param name 要查找的 class 名
1715
+ */
1716
+ export function findParentByClass(el: HTMLElement, name: string): HTMLElement | null {
1717
+ let parent = el.parentNode as HTMLElement;
1718
+ while (parent) {
1719
+ if (!parent.tagName) {
1720
+ continue;
1721
+ }
1722
+ if (parent.tagName.toLowerCase() === 'body') {
1723
+ break;
1724
+ }
1725
+ if (parent.classList.contains(name)) {
1726
+ return parent;
1727
+ }
1728
+ parent = parent.parentNode as HTMLElement;
1729
+ }
1730
+ return null;
1731
+ }
1732
+
1733
+ /**
1734
+ * --- 查找指定 el 的同级所有元素 ---
1735
+ * @param el 基准
1736
+ * @returns HTMLElement[]
1737
+ */
1738
+ export function siblings(el: HTMLElement): HTMLElement[] {
1739
+ if (!el.parentNode) {
1740
+ return [];
1741
+ }
1742
+ const list: HTMLElement[] = [];
1743
+ for (let i = 0; i < el.parentNode.children.length; ++i) {
1744
+ const e = el.parentNode.children.item(i) as HTMLElement;
1745
+ if (e === el) {
1746
+ continue;
1747
+ }
1748
+ list.push(e);
1749
+ }
1750
+ return list;
1751
+ }
1752
+
1753
+ /**
1754
+ * --- 查找指定 el 的同级的存在 data 的元素 ---
1755
+ * @param el 基准
1756
+ * @param name data 名,不含 data-
1757
+ * @returns HTMLElement[]
1758
+ */
1759
+ export function siblingsData(el: HTMLElement, name: string): HTMLElement[] {
1760
+ const list = siblings(el);
1761
+ const olist: HTMLElement[] = [];
1762
+ for (const item of list) {
1763
+ if (item.getAttribute('data-' + name) === null) {
1764
+ continue;
1765
+ }
1766
+ olist.push(item);
1767
+ }
1768
+ return olist;
1769
+ }
1770
+
1771
+ // --- 全屏 ---
1772
+ export function fullscreen(): boolean {
1773
+ const he = document.getElementsByTagName('html')[0] as any;
1774
+ if (he.webkitRequestFullscreen) {
1775
+ he.webkitRequestFullscreen();
1776
+ return true;
1777
+ }
1778
+ else if (he.requestFullscreen) {
1779
+ he.requestFullscreen();
1780
+ return true;
1781
+ }
1782
+ else {
1783
+ return false;
1784
+ }
1785
+ }