@tmagic/stage 1.4.8 → 1.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -8
- package/src/ActionManager.ts +661 -0
- package/src/DragResizeHelper.ts +396 -0
- package/src/MoveableActionsAble.ts +91 -0
- package/src/MoveableOptionsManager.ts +274 -0
- package/src/Rule.ts +173 -0
- package/src/StageCore.ts +417 -0
- package/src/StageDragResize.ts +357 -0
- package/src/StageHighlight.ts +89 -0
- package/src/StageMask.ts +337 -0
- package/src/StageMultiDragResize.ts +229 -0
- package/src/StageRender.ts +252 -0
- package/src/TargetShadow.ts +122 -0
- package/src/const.ts +111 -0
- package/src/index.ts +29 -0
- package/src/logger.ts +37 -0
- package/src/moveable-able.css +108 -0
- package/src/style.css +15 -0
- package/src/types.ts +309 -0
- package/src/util.ts +272 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
|
3
|
+
*
|
|
4
|
+
* Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { GuidesOptions } from '@scena/guides';
|
|
20
|
+
import type { MoveableOptions, OnDragStart } from 'moveable';
|
|
21
|
+
|
|
22
|
+
import Core from '@tmagic/core';
|
|
23
|
+
import type { Id, MApp, MContainer, MNode } from '@tmagic/schema';
|
|
24
|
+
|
|
25
|
+
import { AbleActionEventType, ContainerHighlightType, GuidesType, RenderType, ZIndex } from './const';
|
|
26
|
+
import DragResizeHelper from './DragResizeHelper';
|
|
27
|
+
import StageCore from './StageCore';
|
|
28
|
+
|
|
29
|
+
export type TargetElement = HTMLElement | SVGElement;
|
|
30
|
+
|
|
31
|
+
export type CanSelect = (el: HTMLElement, event: MouseEvent, stop: () => boolean) => boolean | Promise<boolean>;
|
|
32
|
+
export type IsContainer = (el: HTMLElement) => boolean | Promise<boolean>;
|
|
33
|
+
export type CustomizeRender = (renderer: StageCore) => Promise<HTMLElement> | HTMLElement;
|
|
34
|
+
/** 业务方自定义的moveableOptions,可以是配置,也可以是回调函数 */
|
|
35
|
+
export type CustomizeMoveableOptions =
|
|
36
|
+
| ((config?: CustomizeMoveableOptionsCallbackConfig) => MoveableOptions)
|
|
37
|
+
| MoveableOptions
|
|
38
|
+
| undefined;
|
|
39
|
+
/** render提供给的接口,id转成el */
|
|
40
|
+
export type GetTargetElement = (id: Id) => HTMLElement | null;
|
|
41
|
+
/** render提供的接口,通过坐标获得坐标下所有HTML元素数组 */
|
|
42
|
+
export type GetElementsFromPoint = (point: Point) => HTMLElement[];
|
|
43
|
+
export type GetRenderDocument = () => Document | undefined;
|
|
44
|
+
export type DelayedMarkContainer = (event: MouseEvent, exclude: Element[]) => NodeJS.Timeout | undefined;
|
|
45
|
+
export type MarkContainerEnd = () => HTMLElement | null;
|
|
46
|
+
export type GetRootContainer = () => HTMLDivElement | undefined;
|
|
47
|
+
|
|
48
|
+
export type UpdateDragEl = (el: TargetElement, target: TargetElement, container: HTMLElement) => void;
|
|
49
|
+
|
|
50
|
+
export interface StageCoreConfig {
|
|
51
|
+
/** 需要对齐的dom节点的CSS选择器字符串 */
|
|
52
|
+
snapElementQuerySelector?: string;
|
|
53
|
+
/** 放大倍数,默认1倍 */
|
|
54
|
+
zoom?: number;
|
|
55
|
+
canSelect?: CanSelect;
|
|
56
|
+
isContainer?: IsContainer;
|
|
57
|
+
containerHighlightClassName?: string;
|
|
58
|
+
containerHighlightDuration?: number;
|
|
59
|
+
containerHighlightType?: ContainerHighlightType;
|
|
60
|
+
moveableOptions?: CustomizeMoveableOptions;
|
|
61
|
+
/** runtime 的HTML地址,可以是一个HTTP地址,如果和编辑器不同域,需要设置跨域,也可以是一个相对或绝对路径 */
|
|
62
|
+
runtimeUrl?: string;
|
|
63
|
+
render?: (renderer: StageCore) => Promise<HTMLElement> | HTMLElement;
|
|
64
|
+
autoScrollIntoView?: boolean;
|
|
65
|
+
updateDragEl?: UpdateDragEl;
|
|
66
|
+
disabledDragStart?: boolean;
|
|
67
|
+
renderType?: RenderType;
|
|
68
|
+
guidesOptions?: Partial<GuidesOptions>;
|
|
69
|
+
disabledMultiSelect?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ActionManagerConfig {
|
|
73
|
+
container: HTMLElement;
|
|
74
|
+
containerHighlightClassName?: string;
|
|
75
|
+
containerHighlightDuration?: number;
|
|
76
|
+
containerHighlightType?: ContainerHighlightType;
|
|
77
|
+
moveableOptions?: CustomizeMoveableOptions;
|
|
78
|
+
disabledDragStart?: boolean;
|
|
79
|
+
disabledMultiSelect?: boolean;
|
|
80
|
+
canSelect?: CanSelect;
|
|
81
|
+
isContainer?: IsContainer;
|
|
82
|
+
getRootContainer: GetRootContainer;
|
|
83
|
+
getRenderDocument: GetRenderDocument;
|
|
84
|
+
updateDragEl?: UpdateDragEl;
|
|
85
|
+
getTargetElement: GetTargetElement;
|
|
86
|
+
getElementsFromPoint: GetElementsFromPoint;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface MoveableOptionsManagerConfig {
|
|
90
|
+
container: HTMLElement;
|
|
91
|
+
moveableOptions?: CustomizeMoveableOptions;
|
|
92
|
+
getRootContainer: GetRootContainer;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface CustomizeMoveableOptionsCallbackConfig {
|
|
96
|
+
targetEl: HTMLElement | null;
|
|
97
|
+
targetElId?: string;
|
|
98
|
+
targetEls?: HTMLElement[];
|
|
99
|
+
targetElIds?: string[];
|
|
100
|
+
isMulti: boolean;
|
|
101
|
+
document?: Document;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface StageRenderConfig {
|
|
105
|
+
runtimeUrl?: string;
|
|
106
|
+
zoom: number | undefined;
|
|
107
|
+
renderType?: RenderType;
|
|
108
|
+
customizedRender?: () => Promise<HTMLElement | null>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface StageMaskConfig {
|
|
112
|
+
core: StageCore;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface StageDragResizeConfig {
|
|
116
|
+
container: HTMLElement;
|
|
117
|
+
dragResizeHelper: DragResizeHelper;
|
|
118
|
+
moveableOptions?: CustomizeMoveableOptions;
|
|
119
|
+
disabledDragStart?: boolean;
|
|
120
|
+
getRootContainer: GetRootContainer;
|
|
121
|
+
getRenderDocument: GetRenderDocument;
|
|
122
|
+
markContainerEnd: MarkContainerEnd;
|
|
123
|
+
delayedMarkContainer: DelayedMarkContainer;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface StageMultiDragResizeConfig {
|
|
127
|
+
container: HTMLElement;
|
|
128
|
+
dragResizeHelper: DragResizeHelper;
|
|
129
|
+
moveableOptions?: CustomizeMoveableOptions;
|
|
130
|
+
getRootContainer: GetRootContainer;
|
|
131
|
+
getRenderDocument: GetRenderDocument;
|
|
132
|
+
markContainerEnd: MarkContainerEnd;
|
|
133
|
+
delayedMarkContainer: DelayedMarkContainer;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface DragResizeHelperConfig {
|
|
137
|
+
container: HTMLElement;
|
|
138
|
+
updateDragEl?: UpdateDragEl;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export type Rect = {
|
|
142
|
+
width: number;
|
|
143
|
+
height: number;
|
|
144
|
+
} & Offset;
|
|
145
|
+
|
|
146
|
+
export interface Offset {
|
|
147
|
+
left: number;
|
|
148
|
+
top: number;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface Point {
|
|
152
|
+
clientX: number;
|
|
153
|
+
clientY: number;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface GuidesEventData {
|
|
157
|
+
type: GuidesType;
|
|
158
|
+
guides: number[];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface UpdateEventData {
|
|
162
|
+
data: {
|
|
163
|
+
el: HTMLElement;
|
|
164
|
+
style: {
|
|
165
|
+
width?: number;
|
|
166
|
+
height?: number;
|
|
167
|
+
left?: number;
|
|
168
|
+
top?: number;
|
|
169
|
+
transform?: {
|
|
170
|
+
rotate?: string;
|
|
171
|
+
scale?: string;
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
ghostEl?: HTMLElement;
|
|
175
|
+
}[];
|
|
176
|
+
parentEl: HTMLElement | null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface RemoveEventData {
|
|
180
|
+
data: {
|
|
181
|
+
el: HTMLElement;
|
|
182
|
+
}[];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface SortEventData {
|
|
186
|
+
src: Id;
|
|
187
|
+
dist: Id;
|
|
188
|
+
root?: MApp;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface UpdateData {
|
|
192
|
+
config: MNode;
|
|
193
|
+
parent?: MContainer;
|
|
194
|
+
parentId?: Id;
|
|
195
|
+
root: MApp;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export interface RemoveData {
|
|
199
|
+
id: Id;
|
|
200
|
+
parentId: Id;
|
|
201
|
+
root: MApp;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface Runtime {
|
|
205
|
+
getApp?: () => Core | undefined;
|
|
206
|
+
updateRootConfig?: (config: MApp) => void;
|
|
207
|
+
updatePageId?: (id: Id) => void;
|
|
208
|
+
select?: (id: Id) => Promise<HTMLElement> | HTMLElement;
|
|
209
|
+
add?: (data: UpdateData) => void;
|
|
210
|
+
update?: (data: UpdateData) => void;
|
|
211
|
+
sortNode?: (data: SortEventData) => void;
|
|
212
|
+
remove?: (data: RemoveData) => void;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export interface Magic {
|
|
216
|
+
/** 当前页面的根节点变化时调用该方法,编辑器会同步该el和stage的大小,该方法由stage注入到iframe.contentWindow中 */
|
|
217
|
+
onPageElUpdate: (el: HTMLElement) => void;
|
|
218
|
+
|
|
219
|
+
onRuntimeReady: (runtime: Runtime) => void;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface RuntimeWindow extends Window {
|
|
223
|
+
magic: Magic;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export interface StageHighlightConfig {
|
|
227
|
+
container: HTMLElement;
|
|
228
|
+
updateDragEl?: UpdateDragEl;
|
|
229
|
+
getRootContainer: GetRootContainer;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export interface TargetShadowConfig {
|
|
233
|
+
container: HTMLElement;
|
|
234
|
+
zIndex?: ZIndex;
|
|
235
|
+
updateDragEl?: UpdateDragEl;
|
|
236
|
+
idPrefix?: string;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface RuleOptions {
|
|
240
|
+
guidesOptions?: Partial<GuidesOptions>;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export interface CoreEvents {
|
|
244
|
+
mounted: [];
|
|
245
|
+
'runtime-ready': [runtime: Runtime];
|
|
246
|
+
'page-el-update': [el: HTMLElement];
|
|
247
|
+
'change-guides': [data: GuidesEventData];
|
|
248
|
+
select: [selectedEl: HTMLElement, event: MouseEvent];
|
|
249
|
+
'multi-select': [selectedElList: HTMLElement[], event: MouseEvent];
|
|
250
|
+
dblclick: [event: MouseEvent];
|
|
251
|
+
update: [data: UpdateEventData];
|
|
252
|
+
sort: [data: SortEventData];
|
|
253
|
+
'select-parent': [];
|
|
254
|
+
remove: [data: RemoveEventData];
|
|
255
|
+
highlight: [highlightEl: HTMLElement];
|
|
256
|
+
mousemove: [event: MouseEvent];
|
|
257
|
+
mouseleave: [event: MouseEvent];
|
|
258
|
+
'drag-start': [event: OnDragStart];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export interface RenderEvents {
|
|
262
|
+
onload: [];
|
|
263
|
+
'page-el-update': [el: HTMLElement];
|
|
264
|
+
'runtime-ready': [runtime: Runtime];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export interface MaskEvents {
|
|
268
|
+
scroll: [event: WheelEvent];
|
|
269
|
+
'change-guides': [
|
|
270
|
+
data: {
|
|
271
|
+
type: GuidesType.HORIZONTAL;
|
|
272
|
+
guides: number[];
|
|
273
|
+
},
|
|
274
|
+
];
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export interface ActionManagerEvents {
|
|
278
|
+
dblclick: [event: MouseEvent];
|
|
279
|
+
mousemove: [event: MouseEvent];
|
|
280
|
+
mouseleave: [event: MouseEvent];
|
|
281
|
+
highlight: [el: HTMLElement];
|
|
282
|
+
update: [data: UpdateEventData];
|
|
283
|
+
sort: [data: SortEventData];
|
|
284
|
+
remove: [data: RemoveEventData];
|
|
285
|
+
select: [selectedEl: HTMLElement | null, event: MouseEvent];
|
|
286
|
+
'select-parent': [];
|
|
287
|
+
'drag-start': [event: OnDragStart];
|
|
288
|
+
'multi-update': [data: UpdateEventData];
|
|
289
|
+
'change-to-select': [id: Id, event: MouseEvent];
|
|
290
|
+
'before-multi-select': [selectedElList: HTMLElement[]];
|
|
291
|
+
'before-select': [el: HTMLElement, event: MouseEvent];
|
|
292
|
+
'multi-select': [selectedElList: HTMLElement[], event: MouseEvent];
|
|
293
|
+
'get-elements-from-point': [els: HTMLElement[]];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export interface DrEvents {
|
|
297
|
+
'update-moveable': [];
|
|
298
|
+
[AbleActionEventType.REMOVE]: [];
|
|
299
|
+
[AbleActionEventType.SELECT_PARENT]: [];
|
|
300
|
+
'drag-start': [event: OnDragStart];
|
|
301
|
+
update: [data: UpdateEventData];
|
|
302
|
+
sort: [data: SortEventData];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export interface MultiDrEvents {
|
|
306
|
+
'update-moveable': [];
|
|
307
|
+
'change-to-select': [id: Id, event: MouseEvent];
|
|
308
|
+
update: [data: UpdateEventData];
|
|
309
|
+
}
|
package/src/util.ts
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
|
3
|
+
*
|
|
4
|
+
* Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
import { removeClassName } from '@tmagic/utils';
|
|
19
|
+
|
|
20
|
+
import { GHOST_EL_ID_PREFIX, Mode, SELECTED_CLASS, ZIndex } from './const';
|
|
21
|
+
import type { Offset, SortEventData, TargetElement } from './types';
|
|
22
|
+
|
|
23
|
+
const getParents = (el: Element, relative: Element) => {
|
|
24
|
+
let cur: Element | null = el.parentElement;
|
|
25
|
+
const parents: Element[] = [];
|
|
26
|
+
while (cur && cur !== relative) {
|
|
27
|
+
parents.push(cur);
|
|
28
|
+
cur = cur.parentElement;
|
|
29
|
+
}
|
|
30
|
+
return parents;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const getOffset = (el: Element): Offset => {
|
|
34
|
+
const htmlEl = el as HTMLElement;
|
|
35
|
+
const { offsetParent } = htmlEl;
|
|
36
|
+
|
|
37
|
+
const left = htmlEl.offsetLeft || 0;
|
|
38
|
+
const top = htmlEl.offsetTop || 0;
|
|
39
|
+
|
|
40
|
+
// 在 Webkit 中,如果元素为隐藏的(该元素或其祖先元素的 style.display 为 "none"),或者该元素的 style.position 被设为 "fixed",则该属性返回 null。
|
|
41
|
+
// 在 IE 9 中,如果该元素的 style.position 被设置为 "fixed",则该属性返回 null。(display:none 无影响。)
|
|
42
|
+
// body offsetParent 为 null
|
|
43
|
+
if (offsetParent) {
|
|
44
|
+
const parentOffset = getOffset(offsetParent as HTMLElement);
|
|
45
|
+
return {
|
|
46
|
+
left: left + parentOffset.left,
|
|
47
|
+
top: top + parentOffset.top,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
left,
|
|
53
|
+
top,
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// 将蒙层占位节点覆盖在原节点上方
|
|
58
|
+
export const getTargetElStyle = (el: TargetElement, zIndex?: ZIndex) => {
|
|
59
|
+
const offset = getOffset(el);
|
|
60
|
+
const { transform, border } = getComputedStyle(el);
|
|
61
|
+
return `
|
|
62
|
+
position: absolute;
|
|
63
|
+
transform: ${transform};
|
|
64
|
+
left: ${offset.left}px;
|
|
65
|
+
top: ${offset.top}px;
|
|
66
|
+
width: ${el.clientWidth}px;
|
|
67
|
+
height: ${el.clientHeight}px;
|
|
68
|
+
border: ${border};
|
|
69
|
+
opacity: 0;
|
|
70
|
+
${typeof zIndex !== 'undefined' ? `z-index: ${zIndex};` : ''}
|
|
71
|
+
`;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const getAbsolutePosition = (el: HTMLElement, { top, left }: Offset) => {
|
|
75
|
+
const { offsetParent } = el;
|
|
76
|
+
|
|
77
|
+
// 在 Webkit 中,如果元素为隐藏的(该元素或其祖先元素的 style.display 为 "none"),或者该元素的 style.position 被设为 "fixed",则该属性返回 null。
|
|
78
|
+
// 在 IE 9 中,如果该元素的 style.position 被设置为 "fixed",则该属性返回 null。(display:none 无影响。)
|
|
79
|
+
// body offsetParent 为 null
|
|
80
|
+
if (offsetParent) {
|
|
81
|
+
const parentOffset = getOffset(offsetParent as HTMLElement);
|
|
82
|
+
return {
|
|
83
|
+
left: left - parentOffset.left,
|
|
84
|
+
top: top - parentOffset.top,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { left, top };
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const isAbsolute = (style: CSSStyleDeclaration): boolean => style.position === 'absolute';
|
|
92
|
+
|
|
93
|
+
export const isRelative = (style: CSSStyleDeclaration): boolean => style.position === 'relative';
|
|
94
|
+
|
|
95
|
+
export const isStatic = (style: CSSStyleDeclaration): boolean => style.position === 'static';
|
|
96
|
+
|
|
97
|
+
export const isFixed = (style: CSSStyleDeclaration): boolean => style.position === 'fixed';
|
|
98
|
+
|
|
99
|
+
export const isFixedParent = (el: Element) => {
|
|
100
|
+
let fixed = false;
|
|
101
|
+
let dom = el;
|
|
102
|
+
while (dom) {
|
|
103
|
+
fixed = isFixed(getComputedStyle(dom));
|
|
104
|
+
if (fixed) {
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
const { parentElement } = dom;
|
|
108
|
+
if (!parentElement || parentElement.tagName === 'BODY') {
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
dom = parentElement;
|
|
112
|
+
}
|
|
113
|
+
return fixed;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const getMode = (el: Element): Mode => {
|
|
117
|
+
if (isFixedParent(el)) return Mode.FIXED;
|
|
118
|
+
const style = getComputedStyle(el);
|
|
119
|
+
if (isStatic(style) || isRelative(style)) return Mode.SORTABLE;
|
|
120
|
+
return Mode.ABSOLUTE;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const getScrollParent = (element: HTMLElement, includeHidden = false): HTMLElement | null => {
|
|
124
|
+
let style = getComputedStyle(element);
|
|
125
|
+
const overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/;
|
|
126
|
+
|
|
127
|
+
if (isFixed(style)) return null;
|
|
128
|
+
|
|
129
|
+
for (let parent = element; parent.parentElement; ) {
|
|
130
|
+
parent = parent.parentElement;
|
|
131
|
+
|
|
132
|
+
if (parent.tagName === 'HTML') return parent;
|
|
133
|
+
|
|
134
|
+
style = getComputedStyle(parent);
|
|
135
|
+
|
|
136
|
+
if (isAbsolute(style) && isStatic(style)) continue;
|
|
137
|
+
|
|
138
|
+
if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX)) return parent;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return null;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const removeSelectedClassName = (doc: Document) => {
|
|
145
|
+
const oldEl = doc.querySelector(`.${SELECTED_CLASS}`);
|
|
146
|
+
|
|
147
|
+
if (oldEl) {
|
|
148
|
+
removeClassName(oldEl, SELECTED_CLASS);
|
|
149
|
+
if (oldEl.parentNode) removeClassName(oldEl.parentNode as Element, `${SELECTED_CLASS}-parent`);
|
|
150
|
+
doc.querySelectorAll(`.${SELECTED_CLASS}-parents`).forEach((item) => {
|
|
151
|
+
removeClassName(item, `${SELECTED_CLASS}-parents`);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export const addSelectedClassName = (el: Element, doc: Document) => {
|
|
157
|
+
el.classList.add(SELECTED_CLASS);
|
|
158
|
+
(el.parentNode as Element)?.classList.add(`${SELECTED_CLASS}-parent`);
|
|
159
|
+
getParents(el, doc.body).forEach((item) => {
|
|
160
|
+
item.classList.add(`${SELECTED_CLASS}-parents`);
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 下移组件位置
|
|
166
|
+
* @param {number} deltaTop 偏移量
|
|
167
|
+
* @param {Object} detail 当前选中的组件配置
|
|
168
|
+
*/
|
|
169
|
+
export const down = (deltaTop: number, target: TargetElement): SortEventData => {
|
|
170
|
+
let swapIndex = 0;
|
|
171
|
+
let addUpH = target.clientHeight;
|
|
172
|
+
const brothers = Array.from(target.parentNode?.children || []).filter(
|
|
173
|
+
(node) => !node.id.startsWith(GHOST_EL_ID_PREFIX),
|
|
174
|
+
);
|
|
175
|
+
const index = brothers.indexOf(target);
|
|
176
|
+
// 往下移动
|
|
177
|
+
const downEls = brothers.slice(index + 1) as HTMLElement[];
|
|
178
|
+
|
|
179
|
+
for (let i = 0; i < downEls.length; i++) {
|
|
180
|
+
const ele = downEls[i];
|
|
181
|
+
// 是 fixed 不做处理
|
|
182
|
+
if (ele.style?.position === 'fixed') {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
addUpH += ele.clientHeight / 2;
|
|
186
|
+
if (deltaTop <= addUpH) {
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
addUpH += ele.clientHeight / 2;
|
|
190
|
+
swapIndex = i;
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
src: target.id,
|
|
194
|
+
dist: downEls.length && swapIndex > -1 ? downEls[swapIndex].id : target.id,
|
|
195
|
+
};
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 上移组件位置
|
|
200
|
+
* @param {Array} brothers 处于同一容器下的所有子组件配置
|
|
201
|
+
* @param {number} index 当前组件所处的位置
|
|
202
|
+
* @param {number} deltaTop 偏移量
|
|
203
|
+
* @param {Object} detail 当前选中的组件配置
|
|
204
|
+
*/
|
|
205
|
+
export const up = (deltaTop: number, target: TargetElement): SortEventData => {
|
|
206
|
+
const brothers = Array.from(target.parentNode?.children || []).filter(
|
|
207
|
+
(node) => !node.id.startsWith(GHOST_EL_ID_PREFIX),
|
|
208
|
+
);
|
|
209
|
+
const index = brothers.indexOf(target);
|
|
210
|
+
// 往上移动
|
|
211
|
+
const upEls = brothers.slice(0, index) as HTMLElement[];
|
|
212
|
+
|
|
213
|
+
let addUpH = target.clientHeight;
|
|
214
|
+
let swapIndex = upEls.length - 1;
|
|
215
|
+
|
|
216
|
+
for (let i = upEls.length - 1; i >= 0; i--) {
|
|
217
|
+
const ele = upEls[i];
|
|
218
|
+
if (!ele) continue;
|
|
219
|
+
// 是 fixed 不做处理
|
|
220
|
+
if (ele.style.position === 'fixed') continue;
|
|
221
|
+
|
|
222
|
+
addUpH += ele.clientHeight / 2;
|
|
223
|
+
if (-deltaTop <= addUpH) break;
|
|
224
|
+
addUpH += ele.clientHeight / 2;
|
|
225
|
+
|
|
226
|
+
swapIndex = i;
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
src: target.id,
|
|
230
|
+
dist: upEls.length && swapIndex > -1 ? upEls[swapIndex].id : target.id,
|
|
231
|
+
};
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export const isMoveableButton = (target: Element) =>
|
|
235
|
+
target.classList.contains('moveable-button') || target.parentElement?.classList.contains('moveable-button');
|
|
236
|
+
|
|
237
|
+
export const getMarginValue = (el: Element) => {
|
|
238
|
+
if (!el)
|
|
239
|
+
return {
|
|
240
|
+
marginLeft: 0,
|
|
241
|
+
marginTop: 0,
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const { marginLeft, marginTop } = getComputedStyle(el);
|
|
245
|
+
|
|
246
|
+
const marginLeftValue = parseFloat(marginLeft) || 0;
|
|
247
|
+
const marginTopValue = parseFloat(marginTop) || 0;
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
marginLeft: marginLeftValue,
|
|
251
|
+
marginTop: marginTopValue,
|
|
252
|
+
};
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
export const getBorderWidth = (el: Element) => {
|
|
256
|
+
if (!el)
|
|
257
|
+
return {
|
|
258
|
+
borderLeftWidth: 0,
|
|
259
|
+
borderRightWidth: 0,
|
|
260
|
+
borderTopWidth: 0,
|
|
261
|
+
borderBottomWidth: 0,
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const { borderLeftWidth, borderRightWidth, borderTopWidth, borderBottomWidth } = getComputedStyle(el);
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
borderLeftWidth: parseFloat(borderLeftWidth) || 0,
|
|
268
|
+
borderRightWidth: parseFloat(borderRightWidth) || 0,
|
|
269
|
+
borderTopWidth: parseFloat(borderTopWidth) || 0,
|
|
270
|
+
borderBottomWidth: parseFloat(borderBottomWidth) || 0,
|
|
271
|
+
};
|
|
272
|
+
};
|