@leafer-in/editor 1.0.0-rc.3 → 1.0.0-rc.30

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 (41) hide show
  1. package/dist/editor.cjs +1937 -0
  2. package/dist/editor.esm.js +1822 -422
  3. package/dist/editor.esm.min.js +1 -1
  4. package/dist/editor.js +1848 -428
  5. package/dist/editor.min.cjs +1 -0
  6. package/dist/editor.min.js +1 -1
  7. package/package.json +12 -9
  8. package/src/Editor.ts +396 -145
  9. package/src/config.ts +38 -0
  10. package/src/decorator/data.ts +16 -0
  11. package/src/display/EditBox.ts +342 -0
  12. package/src/display/EditMask.ts +37 -0
  13. package/src/display/EditPoint.ts +9 -0
  14. package/src/display/EditSelect.ts +255 -0
  15. package/src/display/SelectArea.ts +30 -0
  16. package/src/display/Stroker.ts +92 -0
  17. package/src/editor/cursor.ts +45 -0
  18. package/src/editor/simulate.ts +14 -0
  19. package/src/editor/target.ts +39 -0
  20. package/src/event/EditorEvent.ts +33 -0
  21. package/src/event/EditorGroupEvent.ts +23 -0
  22. package/src/event/EditorMoveEvent.ts +17 -0
  23. package/src/event/EditorRotateEvent.ts +4 -10
  24. package/src/event/EditorScaleEvent.ts +28 -0
  25. package/src/event/EditorSkewEvent.ts +18 -0
  26. package/src/event/InnerEditorEvent.ts +23 -0
  27. package/src/helper/EditDataHelper.ts +183 -0
  28. package/src/helper/EditSelectHelper.ts +34 -0
  29. package/src/helper/EditorHelper.ts +73 -0
  30. package/src/index.ts +50 -3
  31. package/src/svg.ts +54 -0
  32. package/src/tool/EditTool.ts +99 -0
  33. package/src/tool/EditToolCreator.ts +32 -0
  34. package/src/tool/InnerEditor.ts +68 -0
  35. package/src/tool/LineEditTool.ts +135 -0
  36. package/types/index.d.ts +293 -45
  37. package/src/cursor.ts +0 -57
  38. package/src/event/EditorResizeEvent.ts +0 -34
  39. package/src/resize.ts +0 -87
  40. package/src/tool/LineTool.ts +0 -88
  41. package/src/tool/RectTool.ts +0 -139
@@ -0,0 +1,16 @@
1
+ import { IFunction, ILeaf, IObject } from '@leafer-ui/interface'
2
+ import { defineKey } from '@leafer-ui/draw'
3
+
4
+
5
+ export function targetAttr(fn: IFunction) {
6
+ return (target: ILeaf, key: string) => {
7
+ const privateKey = '_' + key
8
+ defineKey(target, key, {
9
+ get() { return (this as IObject)[privateKey] },
10
+ set(value: unknown) {
11
+ const old = (this as IObject)[privateKey]
12
+ if (old !== value) (this as IObject)[privateKey] = value, fn(this, old)
13
+ }
14
+ } as ThisType<ILeaf>)
15
+ }
16
+ }
@@ -0,0 +1,342 @@
1
+ import { IRect, IEventListenerId, IBoundsData, IPointData, IKeyEvent, IGroup, IBox, IBoxInputData, IAlign } from '@leafer-ui/interface'
2
+ import { Group, Box, AroundHelper, Direction9 } from '@leafer-ui/draw'
3
+ import { DragEvent, PointerEvent, RotateEvent, ZoomEvent } from '@leafer-ui/core'
4
+
5
+ import { IEditBox, IEditor, IEditPoint, IEditPointType } from '@leafer-in/interface'
6
+
7
+ import { updateCursor, updateMoveCursor } from '../editor/cursor'
8
+ import { EditorEvent } from '../event/EditorEvent'
9
+ import { EditPoint } from './EditPoint'
10
+ import { EditDataHelper } from '../helper/EditDataHelper'
11
+
12
+
13
+ const fourDirection = ['top', 'right', 'bottom', 'left']
14
+
15
+ export class EditBox extends Group implements IEditBox {
16
+
17
+ public editor: IEditor
18
+ public dragging: boolean
19
+ public moving: boolean
20
+
21
+ public view: IGroup = new Group() // 放置默认编辑工具控制点
22
+
23
+ public rect: IBox = new Box({ name: 'rect', hitFill: 'all', hitStroke: 'none', strokeAlign: 'center', hitRadius: 5 }) // target rect
24
+ public circle: IEditPoint = new EditPoint({ name: 'circle', strokeAlign: 'center', around: 'center', cursor: 'crosshair', hitRadius: 5 }) // rotate point
25
+
26
+ public buttons: IGroup = new Group({ around: 'center', hitSelf: false })
27
+
28
+ public resizePoints: IEditPoint[] = [] // topLeft, top, topRight, right, bottomRight, bottom, bottomLeft, left
29
+ public rotatePoints: IEditPoint[] = [] // topLeft, top, topRight, right, bottomRight, bottom, bottomLeft, left
30
+ public resizeLines: IEditPoint[] = [] // top, right, bottom, left
31
+
32
+ // fliped
33
+ public get flipped(): boolean { return this.flippedX || this.flippedY }
34
+ public get flippedX(): boolean { return this.scaleX < 0 }
35
+ public get flippedY(): boolean { return this.scaleY < 0 }
36
+ public get flippedOne(): boolean { return this.scaleX * this.scaleY < 0 }
37
+
38
+ public enterPoint: IEditPoint
39
+
40
+ protected __eventIds: IEventListenerId[] = []
41
+
42
+ constructor(editor: IEditor) {
43
+ super()
44
+ this.editor = editor
45
+ this.visible = false
46
+ this.create()
47
+ this.rect.syncEventer = editor // rect的事件不会冒泡,需要手动传递给editor
48
+ this.__listenEvents()
49
+ }
50
+
51
+ public create() {
52
+ let rotatePoint: IEditPoint, resizeLine: IEditPoint, resizePoint: IEditPoint
53
+ const { view, resizePoints, rotatePoints, resizeLines, rect, circle, buttons } = this
54
+ const arounds: IAlign[] = ['bottom-right', 'bottom', 'bottom-left', 'left', 'top-left', 'top', 'top-right', 'right']
55
+
56
+ for (let i = 0; i < 8; i++) {
57
+ rotatePoint = new EditPoint({ name: 'rotate-point', around: arounds[i], width: 15, height: 15, hitFill: "all" })
58
+ rotatePoints.push(rotatePoint)
59
+ this.listenPointEvents(rotatePoint, 'rotate', i)
60
+
61
+ if (i % 2) {
62
+ resizeLine = new EditPoint({ name: 'resize-line', around: 'center', width: 10, height: 10, hitFill: "all" })
63
+ resizeLines.push(resizeLine)
64
+ this.listenPointEvents(resizeLine, 'resize', i)
65
+ }
66
+
67
+ resizePoint = new EditPoint({ name: 'resize-point', hitRadius: 5 })
68
+ resizePoints.push(resizePoint)
69
+ this.listenPointEvents(resizePoint, 'resize', i)
70
+ }
71
+
72
+ buttons.add(circle)
73
+ this.listenPointEvents(circle, 'rotate', 2)
74
+
75
+ view.addMany(...rotatePoints, rect, buttons, ...resizeLines, ...resizePoints)
76
+ this.add(view)
77
+ }
78
+
79
+ public load(): void {
80
+ const { mergeConfig, element, single } = this.editor
81
+ const { rect, circle, resizePoints } = this
82
+ const { stroke, strokeWidth, moveable } = mergeConfig
83
+
84
+ const pointsStyle = this.getPointsStyle()
85
+ const middlePointsStyle = this.getMiddlePointsStyle()
86
+
87
+ let resizeP: IRect
88
+
89
+ for (let i = 0; i < 8; i++) {
90
+ resizeP = resizePoints[i]
91
+ resizeP.set(this.getPointStyle((i % 2) ? middlePointsStyle[((i - 1) / 2) % middlePointsStyle.length] : pointsStyle[(i / 2) % pointsStyle.length]))
92
+ if (!(i % 2)) resizeP.rotation = (i / 2) * 90
93
+ }
94
+
95
+ // rotate
96
+ circle.set(this.getPointStyle(mergeConfig.rotatePoint || pointsStyle[0]))
97
+
98
+ // rect
99
+ rect.set({ stroke, strokeWidth, ...(mergeConfig.rect || {}) })
100
+ rect.hittable = !single && moveable
101
+
102
+ // 编辑框作为底部虚拟元素, 在 onSelect 方法移除
103
+ element.syncEventer = (single && moveable) ? rect : null
104
+ this.app.interaction.bottomList = (single && moveable) ? [{ target: rect, proxy: element }] : null
105
+
106
+ }
107
+
108
+ public update(bounds: IBoundsData): void {
109
+ this.visible = !this.editor.element.locked
110
+
111
+ if (this.view.worldOpacity) {
112
+ const { mergeConfig } = this.editor
113
+ const { width, height } = bounds
114
+ const { rect, circle, resizePoints, rotatePoints, resizeLines } = this
115
+ const { middlePoint, resizeable, rotateable, hideOnSmall } = mergeConfig
116
+
117
+ const smallSize = typeof hideOnSmall === 'number' ? hideOnSmall : 10
118
+ const showPoints = !(hideOnSmall && width < smallSize && height < smallSize)
119
+
120
+ let point = {} as IPointData, rotateP: IRect, resizeP: IRect, resizeL: IRect
121
+
122
+ for (let i = 0; i < 8; i++) {
123
+
124
+ AroundHelper.toPoint(AroundHelper.directionData[i], bounds, point)
125
+ resizeP = resizePoints[i]
126
+ rotateP = rotatePoints[i]
127
+ resizeL = resizeLines[Math.floor(i / 2)]
128
+ resizeP.set(point)
129
+ rotateP.set(point)
130
+ resizeL.set(point)
131
+
132
+ // visible
133
+ resizeP.visible = resizeL.visible = showPoints && !!(resizeable || rotateable)
134
+ rotateP.visible = showPoints && rotateable && resizeable && !mergeConfig.rotatePoint
135
+
136
+ if (i % 2) { // top, right, bottom, left
137
+
138
+ resizeP.visible = rotateP.visible = showPoints && !!middlePoint
139
+
140
+ if (((i + 1) / 2) % 2) { // top, bottom
141
+ resizeL.width = width
142
+ if (resizeP.width > width - 30) resizeP.visible = false
143
+ } else {
144
+ resizeL.height = height
145
+ resizeP.rotation = 90
146
+ if (resizeP.width > height - 30) resizeP.visible = false
147
+ }
148
+ }
149
+
150
+ }
151
+
152
+ // rotate
153
+ circle.visible = showPoints && rotateable && !!mergeConfig.rotatePoint
154
+
155
+ // rect
156
+ if (rect.path) rect.path = null // line可能会变成path优先模式
157
+ rect.set({ ...bounds, visible: true })
158
+
159
+ // buttons
160
+ const buttonVisible = showPoints && (circle.visible || this.buttons.children.length > 1)
161
+ this.buttons.visible = buttonVisible
162
+ if (buttonVisible) this.layoutButtons()
163
+ }
164
+ }
165
+
166
+ protected layoutButtons(): void {
167
+ const { buttons, resizePoints } = this
168
+ const { buttonsDirection, buttonsFixed, buttonsMargin, middlePoint } = this.editor.mergeConfig
169
+
170
+ const { flippedX, flippedY } = this
171
+ let index = fourDirection.indexOf(buttonsDirection)
172
+ if ((index % 2 && flippedX) || ((index + 1) % 2 && flippedY)) {
173
+ if (buttonsFixed) index = (index + 2) % 4 // flip x / y
174
+ }
175
+ const direction = buttonsFixed ? EditDataHelper.getRotateDirection(index, this.flippedOne ? this.rotation : -this.rotation, 4) : index
176
+
177
+ const point = resizePoints[direction * 2 + 1] // 4 map 8 direction
178
+ const useX = direction % 2 // left / right
179
+ const sign = (!direction || direction === 3) ? -1 : 1 // top / left = -1
180
+
181
+ const useWidth = index % 2 // left / right origin direction
182
+ const margin = (buttonsMargin + (useWidth ? ((middlePoint ? point.width : 0) + buttons.boxBounds.width) : ((middlePoint ? point.height : 0) + buttons.boxBounds.height)) / 2) * sign
183
+
184
+ if (useX) {
185
+ buttons.x = point.x + margin
186
+ buttons.y = point.y
187
+ } else {
188
+ buttons.x = point.x
189
+ buttons.y = point.y + margin
190
+ }
191
+
192
+ if (buttonsFixed) {
193
+ buttons.rotation = (direction - index) * 90
194
+ buttons.scaleX = flippedX ? -1 : 1
195
+ buttons.scaleY = flippedY ? -1 : 1
196
+ }
197
+
198
+ }
199
+
200
+ public unload(): void {
201
+ this.visible = false
202
+ }
203
+
204
+
205
+ public getPointStyle(userStyle?: IBoxInputData): IBoxInputData {
206
+ const { stroke, strokeWidth, pointFill, pointSize, pointRadius } = this.editor.mergeConfig
207
+ const defaultStyle = { fill: pointFill, stroke, strokeWidth, around: 'center', strokeAlign: 'center', width: pointSize, height: pointSize, cornerRadius: pointRadius } as IBoxInputData
208
+ return userStyle ? Object.assign(defaultStyle, userStyle) : defaultStyle
209
+ }
210
+
211
+ public getPointsStyle(): IBoxInputData[] {
212
+ const { point } = this.editor.mergeConfig
213
+ return point instanceof Array ? point : [point]
214
+ }
215
+
216
+ public getMiddlePointsStyle(): IBoxInputData[] {
217
+ const { middlePoint } = this.editor.mergeConfig
218
+ return middlePoint instanceof Array ? middlePoint : (middlePoint ? [middlePoint] : this.getPointsStyle())
219
+ }
220
+
221
+ protected onSelect(e: EditorEvent): void {
222
+ if (e.oldList.length === 1) {
223
+ e.oldList[0].syncEventer = null
224
+ if (this.app) this.app.interaction.bottomList = null
225
+ }
226
+ }
227
+
228
+ // drag
229
+
230
+ protected onDragStart(e: DragEvent): void {
231
+ this.dragging = true
232
+ if (e.current.name === 'rect') {
233
+ const { editor } = this
234
+ this.moving = true
235
+ editor.dragStartPoint = { x: editor.element.x, y: editor.element.y }
236
+ editor.opacity = editor.mergeConfig.hideOnMove ? 0 : 1 // move
237
+ }
238
+ }
239
+
240
+ protected onDragEnd(e: DragEvent): void {
241
+ this.dragging = false
242
+ this.moving = false
243
+ if (e.current.name === 'rect') this.editor.opacity = 1 // move
244
+
245
+ }
246
+
247
+ protected onDrag(e: DragEvent): void {
248
+ const { editor } = this
249
+ const point = this.enterPoint = e.current as IEditPoint
250
+ if (point.pointType === 'rotate' || e.metaKey || e.ctrlKey || !editor.mergeConfig.resizeable) {
251
+ if (editor.mergeConfig.rotateable) editor.onRotate(e)
252
+ } else {
253
+ editor.onScale(e)
254
+ }
255
+ updateCursor(editor, e)
256
+ }
257
+
258
+ public onArrow(e: IKeyEvent): void {
259
+ if (this.editor.editing && this.editor.mergeConfig.keyEvent) {
260
+ const move = { x: 0, y: 0 }
261
+ const distance = e.shiftKey ? 10 : 1
262
+ switch (e.code) {
263
+ case 'ArrowDown':
264
+ move.y = distance
265
+ break
266
+ case 'ArrowUp':
267
+ move.y = -distance
268
+ break
269
+ case 'ArrowLeft':
270
+ move.x = -distance
271
+ break
272
+ case 'ArrowRight':
273
+ move.x = distance
274
+ }
275
+ this.editor.move(move)
276
+ }
277
+ }
278
+
279
+
280
+ protected onDoubleTap(e: PointerEvent): void {
281
+ if (this.editor.mergeConfig.openInner === 'double') this.openInner(e)
282
+ }
283
+
284
+ protected onLongPress(e: PointerEvent): void {
285
+ if (this.editor.mergeConfig.openInner === 'long') this.openInner(e)
286
+ }
287
+
288
+ protected openInner(e: PointerEvent): void {
289
+ const { editor } = this
290
+ if (editor.single) {
291
+ const { element } = editor
292
+ if (element.isBranch) {
293
+ editor.openGroup(element as IGroup)
294
+ editor.target = editor.selector.findDeepOne(e)
295
+ } else {
296
+ editor.openInnerEditor()
297
+ }
298
+ }
299
+ }
300
+
301
+
302
+ public listenPointEvents(point: IEditPoint, type: IEditPointType, direction: Direction9): void {
303
+ const { editor } = this
304
+ point.direction = direction
305
+ point.pointType = type
306
+ point.on_(DragEvent.START, this.onDragStart, this)
307
+ point.on_(DragEvent.DRAG, this.onDrag, this)
308
+ point.on_(DragEvent.END, this.onDragEnd, this)
309
+ point.on_(PointerEvent.LEAVE, () => this.enterPoint = null)
310
+ if (point.name !== 'circle') point.on_(PointerEvent.ENTER, (e) => { this.enterPoint = point, updateCursor(editor, e) })
311
+ }
312
+
313
+ protected __listenEvents(): void {
314
+ const { rect, editor } = this
315
+ this.__eventIds = [
316
+ editor.on_(EditorEvent.SELECT, this.onSelect, this),
317
+
318
+ rect.on_(DragEvent.START, this.onDragStart, this),
319
+ rect.on_(DragEvent.DRAG, editor.onMove, editor),
320
+ rect.on_(DragEvent.END, this.onDragEnd, this),
321
+
322
+ rect.on_(ZoomEvent.BEFORE_ZOOM, editor.onScale, editor, true),
323
+ rect.on_(RotateEvent.BEFORE_ROTATE, editor.onRotate, editor, true),
324
+
325
+ rect.on_(PointerEvent.ENTER, () => updateMoveCursor(editor)),
326
+ rect.on_(PointerEvent.DOUBLE_TAP, this.onDoubleTap, this),
327
+ rect.on_(PointerEvent.LONG_PRESS, this.onLongPress, this)
328
+ ]
329
+ }
330
+
331
+ protected __removeListenEvents(): void {
332
+ this.off_(this.__eventIds)
333
+ this.__eventIds.length = 0
334
+ }
335
+
336
+ public destroy(): void {
337
+ this.editor = null
338
+ this.__removeListenEvents()
339
+ super.destroy()
340
+ }
341
+
342
+ }
@@ -0,0 +1,37 @@
1
+ import { ILeaferCanvas, IRenderOptions } from '@leafer-ui/interface'
2
+ import { UI } from '@leafer-ui/draw'
3
+
4
+ import { IEditor } from '@leafer-in/interface'
5
+
6
+
7
+ export class EditMask extends UI {
8
+
9
+ public editor: IEditor
10
+
11
+ constructor(editor: IEditor) {
12
+ super()
13
+ this.editor = editor
14
+ this.hittable = false
15
+ }
16
+
17
+ public __draw(canvas: ILeaferCanvas, options: IRenderOptions): void {
18
+ const { editor } = this
19
+ const { mask } = editor.mergeConfig
20
+
21
+ if (mask && editor.list.length) {
22
+ const { rect } = editor.editBox
23
+ const { width, height } = rect.__
24
+
25
+ canvas.resetTransform()
26
+ canvas.fillWorld(canvas.bounds, mask)
27
+ canvas.setWorld(rect.__world, options.matrix)
28
+ canvas.clearRect(0, 0, width, height)
29
+ }
30
+ }
31
+
32
+ public destroy(): void {
33
+ this.editor = null
34
+ super.destroy()
35
+ }
36
+
37
+ }
@@ -0,0 +1,9 @@
1
+ import { Box, Direction9 } from '@leafer-ui/draw'
2
+
3
+ import { IEditPoint, IEditPointType } from '@leafer-in/interface'
4
+
5
+
6
+ export class EditPoint extends Box implements IEditPoint {
7
+ public direction: Direction9
8
+ public pointType: IEditPointType
9
+ }
@@ -0,0 +1,255 @@
1
+ import { IBounds, ILeaf, ILeafList, IUI, IEventListenerId, IPointerEvent } from '@leafer-ui/interface'
2
+ import { Bounds, LeafList, Group } from '@leafer-ui/draw'
3
+ import { PointerEvent, DragEvent, MoveEvent, ZoomEvent } from '@leafer-ui/core'
4
+
5
+ import { IEditSelect, IEditor, ISelectArea, IStroker } from '@leafer-in/interface'
6
+
7
+ import { Stroker } from './Stroker'
8
+ import { SelectArea } from './SelectArea'
9
+ import { EditSelectHelper } from '../helper/EditSelectHelper'
10
+ import { EditorEvent } from '../event/EditorEvent'
11
+
12
+
13
+ const { findOne } = EditSelectHelper
14
+
15
+ export class EditSelect extends Group implements IEditSelect {
16
+
17
+ public editor: IEditor
18
+
19
+ public get dragging(): boolean { return !!this.originList }
20
+ public get running(): boolean { const { editor } = this; return this.hittable && editor.visible && editor.hittable && editor.mergeConfig.selector }
21
+ public get isMoveMode(): boolean { return this.app && this.app.interaction.moveMode }
22
+
23
+ public hoverStroker: IStroker = new Stroker()
24
+ public targetStroker: IStroker = new Stroker()
25
+
26
+ public bounds: IBounds = new Bounds()
27
+ public selectArea: ISelectArea = new SelectArea()
28
+
29
+ protected originList: ILeafList
30
+ protected needRemoveItem: IUI
31
+
32
+ protected __eventIds: IEventListenerId[] = []
33
+
34
+
35
+ constructor(editor: IEditor) {
36
+ super()
37
+ this.editor = editor
38
+ this.addMany(this.targetStroker, this.hoverStroker, this.selectArea)
39
+ this.__listenEvents()
40
+ }
41
+
42
+ // hover / select
43
+
44
+ protected onHover(): void {
45
+ const { editor } = this
46
+ if (this.running && !this.dragging && !editor.dragging) {
47
+ const { stroke, strokeWidth, hover, hoverStyle } = editor.mergeConfig
48
+ this.hoverStroker.setTarget(hover ? this.editor.hoverTarget : null, { stroke, strokeWidth, ...(hoverStyle || {}) })
49
+ } else {
50
+ this.hoverStroker.target = null
51
+ }
52
+ }
53
+
54
+ protected onSelect(): void {
55
+ if (this.running) {
56
+ const { mergeConfig: config, list } = this.editor
57
+ const { stroke, strokeWidth } = config
58
+ this.targetStroker.setTarget(list, { stroke, strokeWidth: Math.max(1, strokeWidth / 2) })
59
+ this.hoverStroker.target = null
60
+ }
61
+ }
62
+
63
+ public update(): void {
64
+ if (this.targetStroker.target) this.targetStroker.forceUpdate()
65
+ }
66
+
67
+ // move / down
68
+
69
+ protected onPointerMove(e: PointerEvent): void {
70
+ const { app, editor } = this
71
+ if (this.running && !this.isMoveMode && app.config.pointer.hover && !app.interaction.dragging) {
72
+ const find = this.findUI(e)
73
+ editor.hoverTarget = editor.hasItem(find) ? null : find
74
+ } if (this.isMoveMode) {
75
+ editor.hoverTarget = null // move.dragEmpty
76
+ }
77
+ }
78
+
79
+ protected onBeforeDown(e: PointerEvent): void {
80
+ const { select } = this.editor.mergeConfig
81
+ if (select === 'press') this.checkAndSelect(e)
82
+ }
83
+
84
+ protected onTap(e: PointerEvent): void {
85
+ const { editor } = this
86
+ const { select } = editor.mergeConfig
87
+ if (select === 'tap') this.checkAndSelect(e)
88
+
89
+ if (this.needRemoveItem) {
90
+ editor.removeItem(this.needRemoveItem)
91
+ } else if (this.isMoveMode) {
92
+ editor.target = null // move.dragEmpty
93
+ }
94
+
95
+ }
96
+
97
+ protected checkAndSelect(e: PointerEvent): void { // pointer.down or tap
98
+ this.needRemoveItem = null
99
+
100
+ if (this.allowSelect(e)) {
101
+ const { editor } = this
102
+ const find = this.findUI(e)
103
+
104
+ if (find) {
105
+ if (this.isMultipleSelect(e)) {
106
+ if (editor.hasItem(find)) this.needRemoveItem = find // 等待tap事件再实际移除
107
+ else editor.addItem(find)
108
+ } else {
109
+ editor.target = find
110
+ }
111
+
112
+ } else if (this.allow(e.target)) {
113
+
114
+ if (!e.shiftKey) editor.target = null
115
+
116
+ }
117
+ }
118
+ }
119
+
120
+ // drag
121
+
122
+ protected onDragStart(e: DragEvent): void {
123
+ if (this.allowDrag(e)) {
124
+ const { editor } = this
125
+ const { stroke, area } = editor.mergeConfig
126
+ const { x, y } = e.getInner(this)
127
+
128
+ this.bounds.set(x, y)
129
+
130
+ this.selectArea.setStyle({ visible: true, stroke, x, y }, area)
131
+ this.selectArea.setBounds(this.bounds.get())
132
+
133
+ this.originList = editor.leafList.clone()
134
+ }
135
+ }
136
+
137
+ protected onDrag(e: DragEvent): void {
138
+ if (this.editor.dragging) {
139
+ this.onDragEnd()
140
+ return
141
+ }
142
+
143
+ if (this.dragging) {
144
+ const { editor } = this
145
+ const total = e.getInnerTotal(this)
146
+
147
+ const dragBounds = this.bounds.clone().unsign()
148
+ const list = new LeafList(editor.app.find(EditSelectHelper.findBounds, dragBounds))
149
+
150
+ this.bounds.width = total.x
151
+ this.bounds.height = total.y
152
+
153
+ this.selectArea.setBounds(dragBounds.get())
154
+
155
+ if (list.length) {
156
+
157
+ const selectList: ILeaf[] = []
158
+
159
+ this.originList.forEach(item => { if (!list.has(item)) selectList.push(item) })
160
+ list.forEach(item => { if (!this.originList.has(item)) selectList.push(item) })
161
+
162
+ if (selectList.length !== editor.list.length || editor.list.some((child, index) => child !== selectList[index])) {
163
+ editor.target = selectList as IUI[]
164
+ }
165
+
166
+ } else {
167
+
168
+ editor.target = this.originList.list as IUI[]
169
+
170
+ }
171
+ }
172
+ }
173
+
174
+ protected onDragEnd(): void {
175
+ if (this.dragging) this.originList = null, this.selectArea.visible = false
176
+ }
177
+
178
+ protected onAutoMove(e: MoveEvent): void {
179
+ if (this.dragging) {
180
+ const { x, y } = e.getLocalMove(this)
181
+ this.bounds.x += x
182
+ this.bounds.y += y
183
+ }
184
+ }
185
+
186
+ // helper
187
+
188
+ protected allow(target: ILeaf): boolean {
189
+ return target.leafer !== this.editor.leafer
190
+ }
191
+
192
+ protected allowDrag(e: DragEvent) {
193
+ if (this.running && this.editor.mergeConfig.boxSelect && !e.target.draggable) {
194
+ return (!this.editor.editing && this.allow(e.target)) || (e.shiftKey && !findOne(e.path))
195
+ } else {
196
+ return false
197
+ }
198
+ }
199
+
200
+ protected allowSelect(e: IPointerEvent): boolean {
201
+ return this.running && !this.isMoveMode && !e.middle
202
+ }
203
+
204
+ public findDeepOne(e: PointerEvent): IUI {
205
+ const options = { exclude: new LeafList(this.editor.editBox.rect) }
206
+ return findOne(e.target.leafer.interaction.findPath(e, options)) as IUI
207
+ }
208
+
209
+ public findUI(e: PointerEvent): IUI {
210
+ return this.isMultipleSelect(e) ? this.findDeepOne(e) : findOne(e.path)
211
+ }
212
+
213
+ public isMultipleSelect(e: IPointerEvent): boolean {
214
+ return e.shiftKey || this.editor.mergeConfig.continuousSelect
215
+ }
216
+
217
+ protected __listenEvents(): void {
218
+ const { editor } = this
219
+ editor.waitLeafer(() => {
220
+
221
+ const { app } = editor
222
+ app.selector.proxy = editor
223
+
224
+ this.__eventIds = [
225
+ editor.on_(EditorEvent.HOVER, this.onHover, this),
226
+ editor.on_(EditorEvent.SELECT, this.onSelect, this),
227
+
228
+ app.on_(PointerEvent.MOVE, this.onPointerMove, this),
229
+ app.on_(PointerEvent.BEFORE_DOWN, this.onBeforeDown, this),
230
+ app.on_(PointerEvent.TAP, this.onTap, this),
231
+
232
+ app.on_(DragEvent.START, this.onDragStart, this),
233
+ app.on_(DragEvent.DRAG, this.onDrag, this),
234
+ app.on_(DragEvent.END, this.onDragEnd, this),
235
+
236
+ app.on_(MoveEvent.MOVE, this.onAutoMove, this),
237
+ app.on_([ZoomEvent.ZOOM, MoveEvent.MOVE], () => { this.editor.hoverTarget = null }),
238
+ ]
239
+
240
+ })
241
+ }
242
+
243
+ protected __removeListenEvents(): void {
244
+ if (this.__eventIds) {
245
+ this.off_(this.__eventIds)
246
+ this.__eventIds.length = 0
247
+ }
248
+ }
249
+
250
+ public destroy(): void {
251
+ this.editor = this.originList = this.needRemoveItem = null
252
+ this.__removeListenEvents()
253
+ super.destroy()
254
+ }
255
+ }
@@ -0,0 +1,30 @@
1
+ import { IBoundsData, IGroupInputData, IRect, IRectInputData } from '@leafer-ui/interface'
2
+ import { Group, Rect } from '@leafer-ui/draw'
3
+
4
+ import { ISelectArea } from '@leafer-in/interface'
5
+
6
+
7
+ export class SelectArea extends Group implements ISelectArea {
8
+
9
+ protected strokeArea: IRect = new Rect({ strokeAlign: 'center' })
10
+ protected fillArea: IRect = new Rect()
11
+
12
+ constructor(data?: IGroupInputData) {
13
+ super(data)
14
+ this.visible = this.hittable = false
15
+ this.addMany(this.fillArea, this.strokeArea)
16
+ }
17
+
18
+ public setStyle(style: IRectInputData, userStyle?: IRectInputData): void {
19
+ const { visible, stroke, strokeWidth } = style
20
+ this.visible = visible
21
+ this.strokeArea.reset({ stroke, strokeWidth, ...(userStyle || {}) })
22
+ this.fillArea.reset({ visible: userStyle ? false : true, fill: stroke, opacity: 0.2 })
23
+ }
24
+
25
+ public setBounds(bounds: IBoundsData): void {
26
+ this.strokeArea.set(bounds)
27
+ this.fillArea.set(bounds)
28
+ }
29
+
30
+ }